From 4db31acff940dc9cabbd39649103faf4dc2e2c47 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 21 Feb 2019 10:07:21 +0100 Subject: [PATCH 01/59] Begin removing System.Net sources --- .../HttpClientManager/HttpClientManager.cs | 2 +- .../HttpServer/FileWriter.cs | 16 +- .../HttpServer/HttpListenerHost.cs | 3 + .../HttpServer/HttpResultFactory.cs | 2 +- .../HttpServer/RangeRequestWriter.cs | 2 +- .../HttpServer/ResponseFilter.cs | 2 +- .../HttpServer/StreamWriter.cs | 4 +- .../SocketSharp/WebSocketSharpListener.cs | 54 +- .../SocketSharp/WebSocketSharpRequest.cs | 3 +- .../SocketSharp/WebSocketSharpResponse.cs | 46 +- .../BaseProgressiveStreamingService.cs | 18 +- MediaBrowser.Model/Services/IRequest.cs | 2 +- .../Services/QueryParamCollection.cs | 31 + SocketHttpListener/Ext.cs | 1 - SocketHttpListener/HttpResponse.cs | 243 ++++---- .../Net/AuthenticationSchemeSelector.cs | 6 - SocketHttpListener/Net/AuthenticationTypes.cs | 9 - SocketHttpListener/Net/BoundaryType.cs | 11 - SocketHttpListener/Net/ChunkStream.cs | 385 ------------- SocketHttpListener/Net/ChunkedInputStream.cs | 178 ------ SocketHttpListener/Net/EntitySendFormat.cs | 8 - SocketHttpListener/Net/HttpConnection.cs | 528 ----------------- .../Net/HttpEndPointListener.cs | 526 ----------------- SocketHttpListener/Net/HttpEndPointManager.cs | 192 ------- .../Net/HttpKnownHeaderNames.cs | 91 --- SocketHttpListener/Net/HttpListener.cs | 284 --------- .../Net/HttpListenerBasicIdentity.cs | 49 -- .../Net/HttpListenerContext.Managed.cs | 99 ---- SocketHttpListener/Net/HttpListenerContext.cs | 74 --- .../Net/HttpListenerPrefixCollection.cs | 117 ---- .../Net/HttpListenerRequest.Managed.cs | 325 ----------- SocketHttpListener/Net/HttpListenerRequest.cs | 537 ------------------ .../Net/HttpListenerRequestUriBuilder.cs | 443 --------------- .../Net/HttpListenerResponse.Managed.cs | 333 ----------- .../Net/HttpListenerResponse.cs | 294 ---------- .../Net/HttpRequestStream.Managed.cs | 210 ------- SocketHttpListener/Net/HttpRequestStream.cs | 129 ----- .../Net/HttpResponseStream.Managed.cs | 329 ----------- SocketHttpListener/Net/HttpResponseStream.cs | 124 ---- SocketHttpListener/Net/HttpStatusCode.cs | 321 ----------- .../Net/HttpStatusDescription.cs | 69 --- .../Net/HttpStreamAsyncResult.cs | 79 --- SocketHttpListener/Net/HttpVersion.cs | 16 - SocketHttpListener/Net/ListenerPrefix.cs | 89 --- SocketHttpListener/Net/UriScheme.cs | 20 - SocketHttpListener/Net/WebHeaderCollection.cs | 360 ------------ SocketHttpListener/Net/WebHeaderEncoding.cs | 84 --- .../HttpListenerWebSocketContext.cs | 92 --- .../Net/WebSockets/HttpWebSocket.Managed.cs | 81 --- .../Net/WebSockets/HttpWebSocket.cs | 159 ------ .../Net/WebSockets/WebSocketCloseStatus.cs | 27 - .../Net/WebSockets/WebSocketContext.cs | 24 - .../Net/WebSockets/WebSocketValidate.cs | 141 ----- SocketHttpListener/WebSocket.cs | 3 +- 54 files changed, 263 insertions(+), 7012 deletions(-) delete mode 100644 SocketHttpListener/Net/AuthenticationSchemeSelector.cs delete mode 100644 SocketHttpListener/Net/AuthenticationTypes.cs delete mode 100644 SocketHttpListener/Net/BoundaryType.cs delete mode 100644 SocketHttpListener/Net/ChunkStream.cs delete mode 100644 SocketHttpListener/Net/ChunkedInputStream.cs delete mode 100644 SocketHttpListener/Net/EntitySendFormat.cs delete mode 100644 SocketHttpListener/Net/HttpConnection.cs delete mode 100644 SocketHttpListener/Net/HttpEndPointListener.cs delete mode 100644 SocketHttpListener/Net/HttpEndPointManager.cs delete mode 100644 SocketHttpListener/Net/HttpKnownHeaderNames.cs delete mode 100644 SocketHttpListener/Net/HttpListener.cs delete mode 100644 SocketHttpListener/Net/HttpListenerBasicIdentity.cs delete mode 100644 SocketHttpListener/Net/HttpListenerContext.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerContext.cs delete mode 100644 SocketHttpListener/Net/HttpListenerPrefixCollection.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequest.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequest.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs delete mode 100644 SocketHttpListener/Net/HttpListenerResponse.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerResponse.cs delete mode 100644 SocketHttpListener/Net/HttpRequestStream.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpRequestStream.cs delete mode 100644 SocketHttpListener/Net/HttpResponseStream.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpResponseStream.cs delete mode 100644 SocketHttpListener/Net/HttpStatusCode.cs delete mode 100644 SocketHttpListener/Net/HttpStatusDescription.cs delete mode 100644 SocketHttpListener/Net/HttpStreamAsyncResult.cs delete mode 100644 SocketHttpListener/Net/HttpVersion.cs delete mode 100644 SocketHttpListener/Net/ListenerPrefix.cs delete mode 100644 SocketHttpListener/Net/UriScheme.cs delete mode 100644 SocketHttpListener/Net/WebHeaderCollection.cs delete mode 100644 SocketHttpListener/Net/WebHeaderEncoding.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpWebSocket.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketContext.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketValidate.cs diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 2e07281369..834a494f81 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.HttpClientManager } httpWebRequest.ContentType = contentType; - httpWebRequest.ContentLength = bytes.Length; + // httpWebRequest.ContentLength = bytes.Length; (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); } catch (Exception ex) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 7aedba9b31..835c6f52ef 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; @@ -14,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer public class FileWriter : IHttpResult { private ILogger Logger { get; set; } + public IFileSystem FileSystem { get; } private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } @@ -51,6 +53,7 @@ namespace Emby.Server.Implementations.HttpServer Path = path; Logger = logger; + FileSystem = fileSystem; RangeHeader = rangeHeader; Headers["Content-Type"] = contentType; @@ -60,7 +63,8 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { - Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); + // TODO + //Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); StatusCode = HttpStatusCode.OK; } else @@ -95,7 +99,7 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - Headers["Content-Length"] = lengthString; + // TODO Headers["Content-Length"] = lengthString; var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); Headers["Content-Range"] = rangeString; @@ -174,12 +178,12 @@ namespace Emby.Server.Implementations.HttpServer } //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - - await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); + // TODO not DI friendly lol + await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); return; } - - await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); + // TODO not DI friendly lol + await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); } finally { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index ee746c6696..fb42460f1b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -644,6 +644,9 @@ namespace Emby.Server.Implementations.HttpServer { var bOutput = Encoding.UTF8.GetBytes(text); response.SetContentLength(bOutput.Length); + // TODO + response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID + response.SendChunked = true; return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 070717d489..5e9d2b4c36 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -592,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer { if (totalContentLength.HasValue) { - responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); + // TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); } if (isHeadRequest) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 891a76ec2a..9c8ab8d91c 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - Headers["Content-Length"] = RangeLength.ToString(UsCulture); + // TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture); Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index da2bf983a0..dbce3250d4 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.HttpServer public void FilterResponse(IRequest req, IResponse res, object dto) { // Try to prevent compatibility view - res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); + res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization"); res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); res.AddHeader("Access-Control-Allow-Origin", "*"); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index cb2e3580b2..750b0f7958 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer if (source.CanSeek) { - Headers["Content-Length"] = source.Length.ToString(UsCulture); + // TODO Headers["Content-Length"] = source.Length.ToString(UsCulture); } } @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer Headers["Content-Type"] = contentType; - Headers["Content-Length"] = contentLength.ToString(UsCulture); + // TODO Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 693c2328c6..64856f04af 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; + using System.Net; + using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; @@ -13,9 +14,8 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; -using SocketHttpListener.Net; -namespace Jellyfin.Server.SocketSharp + namespace Jellyfin.Server.SocketSharp { public class WebSocketSharpListener : IHttpListener { @@ -67,24 +67,44 @@ namespace Jellyfin.Server.SocketSharp public void Start(IEnumerable urlPrefixes) { - if (_listener == null) - { - _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); - } + // TODO + //if (_listener == null) + //{ + // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); + //} + + //_listener.EnableDualMode = _enableDualMode; + + //if (_certificate != null) + //{ + // _listener.LoadCert(_certificate); + //} - _listener.EnableDualMode = _enableDualMode; + //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); + //_listener.Prefixes.AddRange(urlPrefixes); - if (_certificate != null) + //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); + + //_listener.Start(); + + if (_listener == null) { - _listener.LoadCert(_certificate); + _listener = new HttpListener(); } - + _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - _listener.Prefixes.AddRange(urlPrefixes); - _listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); + //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) @@ -98,8 +118,10 @@ namespace Jellyfin.Server.SocketSharp request.UserAgent ?? string.Empty); } - private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) + 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; @@ -134,7 +156,7 @@ namespace Jellyfin.Server.SocketSharp var endpoint = ctx.Request.RemoteEndPoint.ToString(); var url = ctx.Request.RawUrl; - var queryString = ctx.Request.QueryString; + var queryString = new QueryParamCollection(ctx.Request.QueryString); var connectingArgs = new WebSocketConnectingEventArgs { @@ -153,7 +175,7 @@ namespace Jellyfin.Server.SocketSharp if (WebSocketConnected != null) { - var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger); + SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index 6458707d92..6a00e283c6 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -2,6 +2,7 @@ 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; @@ -438,7 +439,7 @@ namespace Jellyfin.Server.SocketSharp public string UserAgent => request.UserAgent; - public QueryParamCollection Headers => request.Headers; + public QueryParamCollection Headers => new QueryParamCollection(request.Headers); private QueryParamCollection queryString; public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index cf5aee5d40..552ae1754d 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -3,13 +3,14 @@ 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 HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; @@ -53,7 +54,7 @@ namespace Jellyfin.Server.SocketSharp set => _response.ContentType = value; } - public QueryParamCollection Headers => _response.Headers; + public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); private static string AsHeaderValue(Cookie cookie) { @@ -152,7 +153,7 @@ namespace Jellyfin.Server.SocketSharp // 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; + //_response.ContentLength64 = contentLength; } public void SetCookie(Cookie cookie) @@ -172,10 +173,43 @@ namespace Jellyfin.Server.SocketSharp public void ClearCookies() { } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) + const int StreamCopyToBufferSize = 81920; + public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { - return _response.TransmitFile(path, offset, count, fileShareMode, 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); + } + } } } } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 6a98c5e8a6..3e74d59db7 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -324,7 +324,8 @@ namespace MediaBrowser.Api.Playback.Progressive // Seeing cases of -1 here if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) { - responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); + // TODO + //responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); } if (isHeadRequest) @@ -382,17 +383,18 @@ namespace MediaBrowser.Api.Playback.Progressive var hasHeaders = streamResult as IHasHeaders; if (hasHeaders != null) { - if (contentLength.HasValue) - { - hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - else - { + // TODO + //if (contentLength.HasValue) + //{ + // hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); + //} + //else + //{ if (hasHeaders.Headers.ContainsKey("Content-Length")) { hasHeaders.Headers.Remove("Content-Length"); } - } + //} } return streamResult; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index ac9b981b98..909cebce3d 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.Model.Services QueryParamCollection Headers { get; } - Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken); + Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); bool SendChunked { get; set; } } diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 4297b97c66..0e0ebf848b 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using System.Net; using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Services { + // Remove this garbage class, it's just a bastard copy of NameValueCollection public class QueryParamCollection : List { public QueryParamCollection() @@ -20,6 +23,30 @@ namespace MediaBrowser.Model.Services } } + // TODO remove this shit + public QueryParamCollection(WebHeaderCollection webHeaderCollection) + { + foreach (var key in webHeaderCollection.AllKeys) + { + foreach (var value in webHeaderCollection.GetValues(key) ?? Array.Empty()) + { + Add(key, value); + } + } + } + + // TODO remove this shit + public QueryParamCollection(NameValueCollection nameValueCollection) + { + foreach (var key in nameValueCollection.AllKeys) + { + foreach (var value in nameValueCollection.GetValues(key) ?? Array.Empty()) + { + Add(key, value); + } + } + } + private static StringComparison GetStringComparison() { return StringComparison.OrdinalIgnoreCase; @@ -50,6 +77,10 @@ namespace MediaBrowser.Model.Services /// public virtual void Add(string key, string value) { + if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase)) + { + return; + } Add(new NameValuePair(key, value)); } diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs index 2b3c67071c..697c5d5b70 100644 --- a/SocketHttpListener/Ext.cs +++ b/SocketHttpListener/Ext.cs @@ -6,7 +6,6 @@ using System.Net; using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Services; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; using WebSocketState = System.Net.WebSockets.WebSocketState; namespace SocketHttpListener diff --git a/SocketHttpListener/HttpResponse.cs b/SocketHttpListener/HttpResponse.cs index d5d9b4a1c8..a33b942959 100644 --- a/SocketHttpListener/HttpResponse.cs +++ b/SocketHttpListener/HttpResponse.cs @@ -1,123 +1,122 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; -using HttpVersion = SocketHttpListener.Net.HttpVersion; - -namespace SocketHttpListener +using System; +using System.Linq; +using System.Net; +using System.Text; +using MediaBrowser.Model.Services; +using SocketHttpListener.Net; + +namespace SocketHttpListener { - internal class HttpResponse : HttpBase - { - #region Private Fields - - private string _code; - private string _reason; - - #endregion - - #region Private Constructors - - private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) - : base(version, headers) - { - _code = code; - _reason = reason; - } - - #endregion - - #region Internal Constructors - - internal HttpResponse(HttpStatusCode code) - : this(code, code.GetDescription()) - { - } - - internal HttpResponse(HttpStatusCode code, string reason) - : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) - { - Headers["Server"] = "websocket-sharp/1.0"; - } - - #endregion - - #region Public Properties - - public CookieCollection Cookies => GetCookies(Headers, true); - - private static CookieCollection GetCookies(QueryParamCollection headers, bool response) - { - var name = response ? "Set-Cookie" : "Cookie"; - return headers == null || !headers.Contains(name) - ? new CookieCollection() - : CookieHelper.Parse(headers[name], response); - } - - public bool IsProxyAuthenticationRequired => _code == "407"; - - public bool IsUnauthorized => _code == "401"; - - public bool IsWebSocketResponse - { - get - { - var headers = Headers; - return ProtocolVersion > HttpVersion.Version10 && - _code == "101" && - headers.Contains("Upgrade", "websocket") && - headers.Contains("Connection", "Upgrade"); - } - } - - public string Reason => _reason; - - public string StatusCode => _code; - - #endregion - - #region Internal Methods - - internal static HttpResponse CreateCloseResponse(HttpStatusCode code) - { - var res = new HttpResponse(code); - res.Headers["Connection"] = "close"; - - return res; - } - - #endregion - - #region Public Methods - - public void SetCookies(CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var headers = Headers; - var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); - - foreach (var cookie in sorted) - headers.Add("Set-Cookie", cookie.ToString()); - } - - public override string ToString() - { - var output = new StringBuilder(64); - output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); - - var headers = Headers; - foreach (var key in headers.Keys) - output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append(CrLf); - - return output.ToString(); - } - - #endregion - } -} + // TODO what is the point of this class? + internal class HttpResponse : HttpBase + { + #region Private Fields + + private string _code; + private string _reason; + + #endregion + + #region Private Constructors + + private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) + : base(version, headers) + { + _code = code; + _reason = reason; + } + + #endregion + + #region Internal Constructors + + internal HttpResponse(HttpStatusCode code) + : this(code, code.GetDescription()) + { + } + + internal HttpResponse(HttpStatusCode code, string reason) + : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) + { + Headers["Server"] = "websocket-sharp/1.0"; + } + + #endregion + + #region Public Properties + + public CookieCollection Cookies => GetCookies(Headers, true); + + private static CookieCollection GetCookies(QueryParamCollection headers, bool response) + { + var name = response ? "Set-Cookie" : "Cookie"; + return headers == null || !headers.Contains(name) + ? new CookieCollection() + : CookieHelper.Parse(headers[name], response); + } + + public bool IsProxyAuthenticationRequired => _code == "407"; + + public bool IsUnauthorized => _code == "401"; + + public bool IsWebSocketResponse + { + get + { + var headers = Headers; + return ProtocolVersion > HttpVersion.Version10 && + _code == "101" && + headers.Contains("Upgrade", "websocket") && + headers.Contains("Connection", "Upgrade"); + } + } + + public string Reason => _reason; + + public string StatusCode => _code; + + #endregion + + #region Internal Methods + + internal static HttpResponse CreateCloseResponse(HttpStatusCode code) + { + var res = new HttpResponse(code); + res.Headers["Connection"] = "close"; + + return res; + } + + #endregion + + #region Public Methods + + public void SetCookies(CookieCollection cookies) + { + if (cookies == null || cookies.Count == 0) + return; + + var headers = Headers; + var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); + + foreach (var cookie in sorted) + headers.Add("Set-Cookie", cookie.ToString()); + } + + public override string ToString() + { + var output = new StringBuilder(64); + output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); + + var headers = Headers; + foreach (var key in headers.Keys) + output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); + + output.Append(CrLf); + + return output.ToString(); + } + + #endregion + } +} diff --git a/SocketHttpListener/Net/AuthenticationSchemeSelector.cs b/SocketHttpListener/Net/AuthenticationSchemeSelector.cs deleted file mode 100644 index 77e6d0b8ab..0000000000 --- a/SocketHttpListener/Net/AuthenticationSchemeSelector.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Net; - -namespace SocketHttpListener.Net -{ - public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest); -} diff --git a/SocketHttpListener/Net/AuthenticationTypes.cs b/SocketHttpListener/Net/AuthenticationTypes.cs deleted file mode 100644 index a3dbe9dfff..0000000000 --- a/SocketHttpListener/Net/AuthenticationTypes.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal class AuthenticationTypes - { - internal const string NTLM = "NTLM"; - internal const string Negotiate = "Negotiate"; - internal const string Basic = "Basic"; - } -} diff --git a/SocketHttpListener/Net/BoundaryType.cs b/SocketHttpListener/Net/BoundaryType.cs deleted file mode 100644 index da0b22910e..0000000000 --- a/SocketHttpListener/Net/BoundaryType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal enum BoundaryType - { - ContentLength = 0, // Content-Length: XXX - Chunked = 1, // Transfer-Encoding: chunked - Multipart = 3, - None = 4, - Invalid = 5, - } -} diff --git a/SocketHttpListener/Net/ChunkStream.cs b/SocketHttpListener/Net/ChunkStream.cs deleted file mode 100644 index 3836947d4c..0000000000 --- a/SocketHttpListener/Net/ChunkStream.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal sealed class ChunkStream - { - private enum State - { - None, - PartialSize, - Body, - BodyFinished, - Trailer - } - - private class Chunk - { - public byte[] Bytes; - public int Offset; - - public Chunk(byte[] chunk) - { - Bytes = chunk; - } - - public int Read(byte[] buffer, int offset, int size) - { - int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size; - Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread); - Offset += nread; - return nread; - } - } - - internal WebHeaderCollection _headers; - private int _chunkSize; - private int _chunkRead; - private int _totalWritten; - private State _state; - private StringBuilder _saved; - private bool _sawCR; - private bool _gotit; - private int _trailerState; - private List _chunks; - - public ChunkStream(WebHeaderCollection headers) - { - _headers = headers; - _saved = new StringBuilder(); - _chunks = new List(); - _chunkSize = -1; - _totalWritten = 0; - } - - public void ResetBuffer() - { - _chunkSize = -1; - _chunkRead = 0; - _totalWritten = 0; - _chunks.Clear(); - } - - public int Read(byte[] buffer, int offset, int size) - { - return ReadFromChunks(buffer, offset, size); - } - - private int ReadFromChunks(byte[] buffer, int offset, int size) - { - int count = _chunks.Count; - int nread = 0; - - var chunksForRemoving = new List(count); - for (int i = 0; i < count; i++) - { - var chunk = _chunks[i]; - - if (chunk.Offset == chunk.Bytes.Length) - { - chunksForRemoving.Add(chunk); - continue; - } - - nread += chunk.Read(buffer, offset + nread, size - nread); - if (nread == size) - break; - } - - foreach (var chunk in chunksForRemoving) - _chunks.Remove(chunk); - - return nread; - } - - public void Write(byte[] buffer, int offset, int size) - { - // Note, the logic here only works when offset is 0 here. - // Otherwise, it would treat "size" as the end offset instead of an actual byte count from offset. - - if (offset < size) - InternalWrite(buffer, ref offset, size); - } - - private void InternalWrite(byte[] buffer, ref int offset, int size) - { - if (_state == State.None || _state == State.PartialSize) - { - _state = GetChunkSize(buffer, ref offset, size); - if (_state == State.PartialSize) - return; - - _saved.Length = 0; - _sawCR = false; - _gotit = false; - } - - if (_state == State.Body && offset < size) - { - _state = ReadBody(buffer, ref offset, size); - if (_state == State.Body) - return; - } - - if (_state == State.BodyFinished && offset < size) - { - _state = ReadCRLF(buffer, ref offset, size); - if (_state == State.BodyFinished) - return; - - _sawCR = false; - } - - if (_state == State.Trailer && offset < size) - { - _state = ReadTrailer(buffer, ref offset, size); - if (_state == State.Trailer) - return; - - _saved.Length = 0; - _sawCR = false; - _gotit = false; - } - - if (offset < size) - InternalWrite(buffer, ref offset, size); - } - - public bool WantMore => (_chunkRead != _chunkSize || _chunkSize != 0 || _state != State.None); - - public bool DataAvailable - { - get - { - int count = _chunks.Count; - for (int i = 0; i < count; i++) - { - var ch = _chunks[i]; - if (ch == null || ch.Bytes == null) - continue; - if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length) - return (_state != State.Body); - } - return false; - } - } - - public int TotalDataSize => _totalWritten; - - public int ChunkLeft => _chunkSize - _chunkRead; - - private State ReadBody(byte[] buffer, ref int offset, int size) - { - if (_chunkSize == 0) - return State.BodyFinished; - - int diff = size - offset; - if (diff + _chunkRead > _chunkSize) - diff = _chunkSize - _chunkRead; - - byte[] chunk = new byte[diff]; - Buffer.BlockCopy(buffer, offset, chunk, 0, diff); - _chunks.Add(new Chunk(chunk)); - offset += diff; - _chunkRead += diff; - _totalWritten += diff; - - return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body; - } - - private State GetChunkSize(byte[] buffer, ref int offset, int size) - { - _chunkRead = 0; - _chunkSize = 0; - char c = '\0'; - while (offset < size) - { - c = (char)buffer[offset++]; - if (c == '\r') - { - if (_sawCR) - ThrowProtocolViolation("2 CR found"); - - _sawCR = true; - continue; - } - - if (_sawCR && c == '\n') - break; - - if (c == ' ') - _gotit = true; - - if (!_gotit) - _saved.Append(c); - - if (_saved.Length > 20) - ThrowProtocolViolation("chunk size too long."); - } - - if (!_sawCR || c != '\n') - { - if (offset < size) - ThrowProtocolViolation("Missing \\n"); - - try - { - if (_saved.Length > 0) - { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber); - } - } - catch (Exception) - { - ThrowProtocolViolation("Cannot parse chunk size."); - } - - return State.PartialSize; - } - - _chunkRead = 0; - try - { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber); - } - catch (Exception) - { - ThrowProtocolViolation("Cannot parse chunk size."); - } - - if (_chunkSize == 0) - { - _trailerState = 2; - return State.Trailer; - } - - return State.Body; - } - - private static string RemoveChunkExtension(string input) - { - int idx = input.IndexOf(';'); - if (idx == -1) - return input; - return input.Substring(0, idx); - } - - private State ReadCRLF(byte[] buffer, ref int offset, int size) - { - if (!_sawCR) - { - if ((char)buffer[offset++] != '\r') - ThrowProtocolViolation("Expecting \\r"); - - _sawCR = true; - if (offset == size) - return State.BodyFinished; - } - - if (_sawCR && (char)buffer[offset++] != '\n') - ThrowProtocolViolation("Expecting \\n"); - - return State.None; - } - - private State ReadTrailer(byte[] buffer, ref int offset, int size) - { - char c = '\0'; - - // short path - if (_trailerState == 2 && (char)buffer[offset] == '\r' && _saved.Length == 0) - { - offset++; - if (offset < size && (char)buffer[offset] == '\n') - { - offset++; - return State.None; - } - offset--; - } - - int st = _trailerState; - string stString = "\r\n\r"; - while (offset < size && st < 4) - { - c = (char)buffer[offset++]; - if ((st == 0 || st == 2) && c == '\r') - { - st++; - continue; - } - - if ((st == 1 || st == 3) && c == '\n') - { - st++; - continue; - } - - if (st > 0) - { - _saved.Append(stString.Substring(0, _saved.Length == 0 ? st - 2 : st)); - st = 0; - if (_saved.Length > 4196) - ThrowProtocolViolation("Error reading trailer (too long)."); - } - } - - if (st < 4) - { - _trailerState = st; - if (offset < size) - ThrowProtocolViolation("Error reading trailer."); - - return State.Trailer; - } - - var reader = new StringReader(_saved.ToString()); - string line; - while ((line = reader.ReadLine()) != null && line != "") - _headers.Add(line); - - return State.None; - } - - private static void ThrowProtocolViolation(string message) - { - var we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null); - throw we; - } - } -} diff --git a/SocketHttpListener/Net/ChunkedInputStream.cs b/SocketHttpListener/Net/ChunkedInputStream.cs deleted file mode 100644 index 7aa8529e38..0000000000 --- a/SocketHttpListener/Net/ChunkedInputStream.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.IO; -using System.Net; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal sealed class ChunkedInputStream : HttpRequestStream - { - private ChunkStream _decoder; - private readonly HttpListenerContext _context; - private bool _no_more_data; - - private class ReadBufferState - { - public byte[] Buffer; - public int Offset; - public int Count; - public int InitialCount; - public HttpStreamAsyncResult Ares; - public ReadBufferState(byte[] buffer, int offset, int count, HttpStreamAsyncResult ares) - { - Buffer = buffer; - Offset = offset; - Count = count; - InitialCount = count; - Ares = ares; - } - } - - public ChunkedInputStream(HttpListenerContext context, Stream stream, byte[] buffer, int offset, int length) - : base(stream, buffer, offset, length) - { - _context = context; - var coll = (WebHeaderCollection)context.Request.Headers; - _decoder = new ChunkStream(coll); - } - - public ChunkStream Decoder - { - get => _decoder; - set => _decoder = value; - } - - protected override int ReadCore(byte[] buffer, int offset, int count) - { - IAsyncResult ares = BeginReadCore(buffer, offset, count, null, null); - return EndRead(ares); - } - - protected override IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - if (_no_more_data || size == 0 || _closed) - { - ares.Complete(); - return ares; - } - int nread = _decoder.Read(buffer, offset, size); - offset += nread; - size -= nread; - if (size == 0) - { - // got all we wanted, no need to bother the decoder yet - ares._count = nread; - ares.Complete(); - return ares; - } - if (!_decoder.WantMore) - { - _no_more_data = nread == 0; - ares._count = nread; - ares.Complete(); - return ares; - } - ares._buffer = new byte[8192]; - ares._offset = 0; - ares._count = 8192; - var rb = new ReadBufferState(buffer, offset, size, ares); - rb.InitialCount += nread; - base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb); - return ares; - } - - private void OnRead(IAsyncResult base_ares) - { - ReadBufferState rb = (ReadBufferState)base_ares.AsyncState; - var ares = rb.Ares; - try - { - int nread = base.EndRead(base_ares); - if (nread == 0) - { - _no_more_data = true; - ares._count = rb.InitialCount - rb.Count; - ares.Complete(); - return; - } - - _decoder.Write(ares._buffer, ares._offset, nread); - nread = _decoder.Read(rb.Buffer, rb.Offset, rb.Count); - rb.Offset += nread; - rb.Count -= nread; - if (rb.Count == 0 || !_decoder.WantMore) - { - _no_more_data = !_decoder.WantMore && nread == 0; - ares._count = rb.InitialCount - rb.Count; - ares.Complete(); - return; - } - ares._offset = 0; - ares._count = Math.Min(8192, _decoder.ChunkLeft + 6); - base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb); - } - catch (Exception e) - { - _context.Connection.SendError(e.Message, 400); - ares.Complete(e); - } - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - var ares = asyncResult as HttpStreamAsyncResult; - if (ares == null || !ReferenceEquals(this, ares._parent)) - { - throw new ArgumentException("Invalid async result"); - } - if (ares._endCalled) - { - throw new InvalidOperationException("Invalid end call"); - } - ares._endCalled = true; - - if (!asyncResult.IsCompleted) - asyncResult.AsyncWaitHandle.WaitOne(); - - if (ares._error != null) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "Operation aborted"); - - return ares._count; - } - } -} diff --git a/SocketHttpListener/Net/EntitySendFormat.cs b/SocketHttpListener/Net/EntitySendFormat.cs deleted file mode 100644 index 3ed981084d..0000000000 --- a/SocketHttpListener/Net/EntitySendFormat.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal enum EntitySendFormat - { - ContentLength = 0, // Content-Length: XXX - Chunked = 1, // Transfer-Encoding: chunked - } -} diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs deleted file mode 100644 index e89f4ed9b1..0000000000 --- a/SocketHttpListener/Net/HttpConnection.cs +++ /dev/null @@ -1,528 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; -namespace SocketHttpListener.Net -{ - sealed class HttpConnection - { - private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); - const int BufferSize = 8192; - Socket _socket; - Stream _stream; - HttpEndPointListener _epl; - MemoryStream _memoryStream; - byte[] _buffer; - HttpListenerContext _context; - StringBuilder _currentLine; - ListenerPrefix _prefix; - HttpRequestStream _requestStream; - HttpResponseStream _responseStream; - bool _chunked; - int _reuses; - bool _contextBound; - bool secure; - IPEndPoint local_ep; - HttpListener _lastListener; - X509Certificate cert; - SslStream ssl_stream; - - private readonly ILogger _logger; - private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, - X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem, - IEnvironmentInfo environment) - { - _logger = logger; - this._socket = socket; - this._epl = epl; - this.secure = secure; - this.cert = cert; - _cryptoProvider = cryptoProvider; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - if (secure == false) - { - _stream = new SocketStream(_socket, false); - } - else - { - ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) => - { - if (c == null) - { - return true; - } - - //var c2 = c as X509Certificate2; - //if (c2 == null) - //{ - // c2 = new X509Certificate2(c.GetRawCertData()); - //} - - //_clientCert = c2; - //_clientCertErrors = new int[] { (int)e }; - return true; - }); - - _stream = ssl_stream; - } - } - - public Stream Stream => _stream; - - public async Task Init() - { - if (ssl_stream != null) - { - var enableAsync = true; - if (enableAsync) - { - await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false); - } - else - { - ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); - } - } - - InitInternal(); - } - - private void InitInternal() - { - _contextBound = false; - _requestStream = null; - _responseStream = null; - _prefix = null; - _chunked = false; - _memoryStream = new MemoryStream(); - _position = 0; - _inputState = InputState.RequestLine; - _lineState = LineState.None; - _context = new HttpListenerContext(this); - } - - public bool IsClosed => (_socket == null); - - public int Reuses => _reuses; - - public IPEndPoint LocalEndPoint - { - get - { - if (local_ep != null) - return local_ep; - - local_ep = (IPEndPoint)_socket.LocalEndPoint; - return local_ep; - } - } - - public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; - - public bool IsSecure => secure; - - public ListenerPrefix Prefix - { - get => _prefix; - set => _prefix = value; - } - - private void OnTimeout(object unused) - { - //_logger.LogInformation("HttpConnection timer fired"); - CloseSocket(); - Unbind(); - } - - public void BeginReadRequest() - { - if (_buffer == null) - _buffer = new byte[BufferSize]; - try - { - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - catch - { - CloseSocket(); - Unbind(); - } - } - - public HttpRequestStream GetRequestStream(bool chunked, long contentlength) - { - if (_requestStream == null) - { - byte[] buffer = _memoryStream.GetBuffer(); - int length = (int)_memoryStream.Length; - _memoryStream = null; - if (chunked) - { - _chunked = true; - //_context.Response.SendChunked = true; - _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position); - } - else - { - _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength); - } - } - return _requestStream; - } - - public HttpResponseStream GetResponseStream(bool isExpect100Continue = false) - { - // TODO: can we get this _stream before reading the input? - if (_responseStream == null) - { - var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure; - - _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger); - } - return _responseStream; - } - - private static void OnRead(IAsyncResult ares) - { - var cnc = (HttpConnection)ares.AsyncState; - cnc.OnReadInternal(ares); - } - - private void OnReadInternal(IAsyncResult ares) - { - int nread = -1; - try - { - nread = _stream.EndRead(ares); - _memoryStream.Write(_buffer, 0, nread); - if (_memoryStream.Length > 32768) - { - SendError("Bad Request", 400); - Close(true); - return; - } - } - catch - { - if (_memoryStream != null && _memoryStream.Length > 0) - SendError(); - if (_socket != null) - { - CloseSocket(); - Unbind(); - } - return; - } - - if (nread == 0) - { - CloseSocket(); - Unbind(); - return; - } - - if (ProcessInput(_memoryStream)) - { - if (!_context.HaveError) - _context.Request.FinishInitialization(); - - if (_context.HaveError) - { - SendError(); - Close(true); - return; - } - - if (!_epl.BindContext(_context)) - { - const int NotFoundErrorCode = 404; - SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode); - Close(true); - return; - } - HttpListener listener = _epl.Listener; - if (_lastListener != listener) - { - RemoveConnection(); - listener.AddConnection(this); - _lastListener = listener; - } - - _contextBound = true; - listener.RegisterContext(_context); - return; - } - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - - private void RemoveConnection() - { - if (_lastListener == null) - _epl.RemoveConnection(this); - else - _lastListener.RemoveConnection(this); - } - - private enum InputState - { - RequestLine, - Headers - } - - private enum LineState - { - None, - CR, - LF - } - - InputState _inputState = InputState.RequestLine; - LineState _lineState = LineState.None; - int _position; - - // true -> done processing - // false -> need more input - private bool ProcessInput(MemoryStream ms) - { - byte[] buffer = ms.GetBuffer(); - int len = (int)ms.Length; - int used = 0; - string line; - - while (true) - { - if (_context.HaveError) - return true; - - if (_position >= len) - break; - - try - { - line = ReadLine(buffer, _position, len - _position, ref used); - _position += used; - } - catch - { - _context.ErrorMessage = "Bad request"; - _context.ErrorStatus = 400; - return true; - } - - if (line == null) - break; - - if (line == "") - { - if (_inputState == InputState.RequestLine) - continue; - _currentLine = null; - ms = null; - return true; - } - - if (_inputState == InputState.RequestLine) - { - _context.Request.SetRequestLine(line); - _inputState = InputState.Headers; - } - else - { - try - { - _context.Request.AddHeader(line); - } - catch (Exception e) - { - _context.ErrorMessage = e.Message; - _context.ErrorStatus = 400; - return true; - } - } - } - - if (used == len) - { - ms.SetLength(0); - _position = 0; - } - return false; - } - - private string ReadLine(byte[] buffer, int offset, int len, ref int used) - { - if (_currentLine == null) - _currentLine = new StringBuilder(128); - int last = offset + len; - used = 0; - for (int i = offset; i < last && _lineState != LineState.LF; i++) - { - used++; - byte b = buffer[i]; - if (b == 13) - { - _lineState = LineState.CR; - } - else if (b == 10) - { - _lineState = LineState.LF; - } - else - { - _currentLine.Append((char)b); - } - } - - string result = null; - if (_lineState == LineState.LF) - { - _lineState = LineState.None; - result = _currentLine.ToString(); - _currentLine.Length = 0; - } - - return result; - } - - public void SendError(string msg, int status) - { - try - { - HttpListenerResponse response = _context.Response; - response.StatusCode = status; - response.ContentType = "text/html"; - string description = HttpStatusDescription.Get(status); - string str; - if (msg != null) - str = string.Format("

{0} ({1})

", description, msg); - else - str = string.Format("

{0}

", description); - - byte[] error = Encoding.UTF8.GetBytes(str); - response.Close(error, false); - } - catch - { - // response was already closed - } - } - - public void SendError() - { - SendError(_context.ErrorMessage, _context.ErrorStatus); - } - - private void Unbind() - { - if (_contextBound) - { - _epl.UnbindContext(_context); - _contextBound = false; - } - } - - public void Close() - { - Close(false); - } - - private void CloseSocket() - { - if (_socket == null) - return; - - try - { - _socket.Close(); - } - catch { } - finally - { - _socket = null; - } - - RemoveConnection(); - } - - internal void Close(bool force) - { - if (_socket != null) - { - Stream st = GetResponseStream(); - if (st != null) - st.Close(); - - _responseStream = null; - } - - if (_socket != null) - { - force |= !_context.Request.KeepAlive; - if (!force) - force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase)); - - if (!force && _context.Request.FlushInput()) - { - if (_chunked && _context.Response.ForceCloseChunked == false) - { - // Don't close. Keep working. - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - Socket s = _socket; - _socket = null; - try - { - if (s != null) - s.Shutdown(SocketShutdown.Both); - } - catch - { - } - finally - { - if (s != null) - { - try - { - s.Close(); - } - catch { } - } - } - Unbind(); - RemoveConnection(); - return; - } - } - } -} diff --git a/SocketHttpListener/Net/HttpEndPointListener.cs b/SocketHttpListener/Net/HttpEndPointListener.cs deleted file mode 100644 index 35d1af6486..0000000000 --- a/SocketHttpListener/Net/HttpEndPointListener.cs +++ /dev/null @@ -1,526 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - internal sealed class HttpEndPointListener - { - private HttpListener _listener; - private IPEndPoint _endpoint; - private Socket _socket; - private Dictionary _prefixes; - private List _unhandledPrefixes; // host = '*' - private List _allPrefixes; // host = '+' - private X509Certificate _cert; - private bool _secure; - private Dictionary _unregisteredConnections; - - private readonly ILogger _logger; - private bool _closed; - private bool _enableDualMode; - private readonly ICryptoProvider _cryptoProvider; - private readonly ISocketFactory _socketFactory; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, - ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, - IFileSystem fileSystem, IEnvironmentInfo environment) - { - this._listener = listener; - _logger = logger; - _cryptoProvider = cryptoProvider; - _socketFactory = socketFactory; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - this._secure = secure; - this._cert = cert; - - _enableDualMode = addr.Equals(IPAddress.IPv6Any); - _endpoint = new IPEndPoint(addr, port); - - _prefixes = new Dictionary(); - _unregisteredConnections = new Dictionary(); - - CreateSocket(); - } - - internal HttpListener Listener => _listener; - - private void CreateSocket() - { - try - { - _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); - } - catch (SocketCreateException ex) - { - if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) && - (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono 4.8.1 and lower on bsd is throwing this - string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono 5.2 on bsd is throwing this - string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase))) - { - _endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port); - _enableDualMode = false; - _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); - } - else - { - throw; - } - } - - try - { - _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - } - catch (SocketException) - { - // This is not supported on all operating systems (qnap) - } - - _socket.Bind(_endpoint); - - // This is the number TcpListener uses. - _socket.Listen(2147483647); - - Accept(); - - _closed = false; - } - - private void Accept() - { - var acceptEventArg = new SocketAsyncEventArgs(); - acceptEventArg.UserToken = this; - acceptEventArg.Completed += new EventHandler(OnAccept); - - Accept(acceptEventArg); - } - - private static void TryCloseAndDispose(Socket socket) - { - try - { - using (socket) - { - socket.Close(); - } - } - catch - { - - } - } - - private static void TryClose(Socket socket) - { - try - { - socket.Close(); - } - catch - { - - } - } - - private void Accept(SocketAsyncEventArgs acceptEventArg) - { - // acceptSocket must be cleared since the context object is being reused - acceptEventArg.AcceptSocket = null; - - try - { - bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg); - - if (!willRaiseEvent) - { - ProcessAccept(acceptEventArg); - } - } - catch (ObjectDisposedException) - { - // TODO Investigate or properly fix. - } - catch (Exception ex) - { - var epl = (HttpEndPointListener)acceptEventArg.UserToken; - - epl._logger.LogError(ex, "Error in socket.AcceptAsync"); - } - } - - // This method is the callback method associated with Socket.AcceptAsync - // operations and is invoked when an accept operation is complete - // - private static void OnAccept(object sender, SocketAsyncEventArgs e) - { - ProcessAccept(e); - } - - private static async void ProcessAccept(SocketAsyncEventArgs args) - { - var epl = (HttpEndPointListener)args.UserToken; - - if (epl._closed) - { - return; - } - - // http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx - // Under certain conditions ConnectionReset can occur - // Need to attept to re-accept - var socketError = args.SocketError; - var accepted = args.AcceptSocket; - - epl.Accept(args); - - if (socketError == SocketError.ConnectionReset) - { - epl._logger.LogError("SocketError.ConnectionReset reported. Attempting to re-accept."); - return; - } - - if (accepted == null) - { - return; - } - - if (epl._secure && epl._cert == null) - { - TryClose(accepted); - return; - } - - try - { - var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString(); - var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString(); - //_logger.LogInformation("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure); - - var conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._fileSystem, epl._environment); - - await conn.Init().ConfigureAwait(false); - - //_logger.LogDebug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); - lock (epl._unregisteredConnections) - { - epl._unregisteredConnections[conn] = conn; - } - conn.BeginReadRequest(); - } - catch (Exception ex) - { - epl._logger.LogError(ex, "Error in ProcessAccept"); - - TryClose(accepted); - epl.Accept(); - return; - } - } - - private Socket CreateSocket(AddressFamily addressFamily, bool dualMode) - { - try - { - var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - - if (dualMode) - { - socket.DualMode = true; - } - - return socket; - } - catch (SocketException ex) - { - throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); - } - catch (ArgumentException ex) - { - if (dualMode) - { - // Mono for BSD incorrectly throws ArgumentException instead of SocketException - throw new SocketCreateException("AddressFamilyNotSupported", ex); - } - else - { - throw; - } - } - } - - internal void RemoveConnection(HttpConnection conn) - { - lock (_unregisteredConnections) - { - _unregisteredConnections.Remove(conn); - } - } - - public bool BindContext(HttpListenerContext context) - { - var req = context.Request; - HttpListener listener = SearchListener(req.Url, out var prefix); - if (listener == null) - return false; - - context.Connection.Prefix = prefix; - return true; - } - - public void UnbindContext(HttpListenerContext context) - { - if (context == null || context.Request == null) - return; - - _listener.UnregisterContext(context); - } - - private HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) - { - prefix = null; - if (uri == null) - return null; - - string host = uri.Host; - int port = uri.Port; - string path = WebUtility.UrlDecode(uri.AbsolutePath); - string pathSlash = path[path.Length - 1] == '/' ? path : path + "/"; - - HttpListener bestMatch = null; - int bestLength = -1; - - if (host != null && host != "") - { - Dictionary localPrefixes = _prefixes; - foreach (var p in localPrefixes.Keys) - { - string ppath = p.Path; - if (ppath.Length < bestLength) - continue; - - if (p.Host != host || p.Port != port) - continue; - - if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath)) - { - bestLength = ppath.Length; - bestMatch = localPrefixes[p]; - prefix = p; - } - } - if (bestLength != -1) - return bestMatch; - } - - List list = _unhandledPrefixes; - bestMatch = MatchFromList(host, path, list, out prefix); - - if (path != pathSlash && bestMatch == null) - bestMatch = MatchFromList(host, pathSlash, list, out prefix); - - if (bestMatch != null) - return bestMatch; - - list = _allPrefixes; - bestMatch = MatchFromList(host, path, list, out prefix); - - if (path != pathSlash && bestMatch == null) - bestMatch = MatchFromList(host, pathSlash, list, out prefix); - - if (bestMatch != null) - return bestMatch; - - return null; - } - - private HttpListener MatchFromList(string host, string path, List list, out ListenerPrefix prefix) - { - prefix = null; - if (list == null) - return null; - - HttpListener bestMatch = null; - int bestLength = -1; - - foreach (ListenerPrefix p in list) - { - string ppath = p.Path; - if (ppath.Length < bestLength) - continue; - - if (path.StartsWith(ppath)) - { - bestLength = ppath.Length; - bestMatch = p._listener; - prefix = p; - } - } - - return bestMatch; - } - - private void AddSpecial(List list, ListenerPrefix prefix) - { - if (list == null) - return; - - foreach (ListenerPrefix p in list) - { - if (p.Path == prefix.Path) - throw new Exception("net_listener_already"); - } - list.Add(prefix); - } - - private bool RemoveSpecial(List list, ListenerPrefix prefix) - { - if (list == null) - return false; - - int c = list.Count; - for (int i = 0; i < c; i++) - { - ListenerPrefix p = list[i]; - if (p.Path == prefix.Path) - { - list.RemoveAt(i); - return true; - } - } - return false; - } - - private void CheckIfRemove() - { - if (_prefixes.Count > 0) - return; - - List list = _unhandledPrefixes; - if (list != null && list.Count > 0) - return; - - list = _allPrefixes; - if (list != null && list.Count > 0) - return; - - HttpEndPointManager.RemoveEndPoint(this, _endpoint); - } - - public void Close() - { - _closed = true; - _socket.Close(); - lock (_unregisteredConnections) - { - // Clone the list because RemoveConnection can be called from Close - var connections = new List(_unregisteredConnections.Keys); - - foreach (HttpConnection c in connections) - c.Close(true); - _unregisteredConnections.Clear(); - } - } - - public void AddPrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = _unhandledPrefixes; - future = current != null ? new List(current) : new List(); - prefix._listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); - return; - } - - if (prefix.Host == "+") - { - do - { - current = _allPrefixes; - future = current != null ? new List(current) : new List(); - prefix._listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); - return; - } - - Dictionary prefs, p2; - do - { - prefs = _prefixes; - if (prefs.ContainsKey(prefix)) - { - throw new Exception("net_listener_already"); - } - p2 = new Dictionary(prefs); - p2[prefix] = listener; - } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); - } - - public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = _unhandledPrefixes; - future = current != null ? new List(current) : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); - - CheckIfRemove(); - return; - } - - if (prefix.Host == "+") - { - do - { - current = _allPrefixes; - future = current != null ? new List(current) : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); - CheckIfRemove(); - return; - } - - Dictionary prefs, p2; - do - { - prefs = _prefixes; - if (!prefs.ContainsKey(prefix)) - break; - - p2 = new Dictionary(prefs); - p2.Remove(prefix); - } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); - CheckIfRemove(); - } - } -} diff --git a/SocketHttpListener/Net/HttpEndPointManager.cs b/SocketHttpListener/Net/HttpEndPointManager.cs deleted file mode 100644 index 27b4244a6c..0000000000 --- a/SocketHttpListener/Net/HttpEndPointManager.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - internal sealed class HttpEndPointManager - { - private static Dictionary> s_ipEndPoints = new Dictionary>(); - - private HttpEndPointManager() - { - } - - public static void AddListener(ILogger logger, HttpListener listener) - { - var added = new List(); - try - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - foreach (string prefix in listener.Prefixes) - { - AddPrefixInternal(logger, prefix, listener); - added.Add(prefix); - } - } - } - catch - { - foreach (string prefix in added) - { - RemovePrefix(logger, prefix, listener); - } - throw; - } - } - - public static void AddPrefix(ILogger logger, string prefix, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - AddPrefixInternal(logger, prefix, listener); - } - } - - private static void AddPrefixInternal(ILogger logger, string p, HttpListener listener) - { - int start = p.IndexOf(':') + 3; - int colon = p.IndexOf(':', start); - if (colon != -1) - { - // root can't be -1 here, since we've already checked for ending '/' in ListenerPrefix. - int root = p.IndexOf('/', colon, p.Length - colon); - string portString = p.Substring(colon + 1, root - colon - 1); - - if (!int.TryParse(portString, out var port) || port <= 0 || port >= 65536) - { - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port"); - } - } - - var lp = new ListenerPrefix(p); - if (lp.Host != "*" && lp.Host != "+" && Uri.CheckHostName(lp.Host) == UriHostNameType.Unknown) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_listener_host"); - - if (lp.Path.IndexOf('%') != -1) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); - - // listens on all the interfaces if host name cannot be parsed by IPAddress. - HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); - epl.AddPrefix(lp, listener); - } - - private static IPAddress GetIpAnyAddress(HttpListener listener) - { - return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any; - } - - private static HttpEndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) - { - IPAddress addr; - if (host == "*" || host == "+") - { - addr = GetIpAnyAddress(listener); - } - else - { - const int NotSupportedErrorCode = 50; - try - { - addr = Dns.GetHostAddresses(host)[0]; - } - catch - { - // Throw same error code as windows, request is not supported. - throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); - } - - if (IPAddress.Any.Equals(addr)) - { - // Don't support listening to 0.0.0.0, match windows behavior. - throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); - } - } - - Dictionary p = null; - if (s_ipEndPoints.ContainsKey(addr)) - { - p = s_ipEndPoints[addr]; - } - else - { - p = new Dictionary(); - s_ipEndPoints[addr] = p; - } - - HttpEndPointListener epl = null; - if (p.ContainsKey(port)) - { - epl = p[port]; - } - else - { - try - { - epl = new HttpEndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.StreamHelper, listener.FileSystem, listener.EnvironmentInfo); - } - catch (SocketException ex) - { - throw new HttpListenerException(ex.ErrorCode, ex.Message); - } - p[port] = epl; - } - - return epl; - } - - public static void RemoveEndPoint(HttpEndPointListener epl, IPEndPoint ep) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - Dictionary p = null; - p = s_ipEndPoints[ep.Address]; - p.Remove(ep.Port); - if (p.Count == 0) - { - s_ipEndPoints.Remove(ep.Address); - } - epl.Close(); - } - } - - public static void RemoveListener(ILogger logger, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - foreach (string prefix in listener.Prefixes) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - } - - public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - - private static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener) - { - var lp = new ListenerPrefix(prefix); - if (lp.Path.IndexOf('%') != -1) - return; - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - return; - - HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); - epl.RemovePrefix(lp, listener); - } - } -} diff --git a/SocketHttpListener/Net/HttpKnownHeaderNames.cs b/SocketHttpListener/Net/HttpKnownHeaderNames.cs deleted file mode 100644 index dc6f2ce415..0000000000 --- a/SocketHttpListener/Net/HttpKnownHeaderNames.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static partial class HttpKnownHeaderNames - { - // When adding a new constant, add it to HttpKnownHeaderNames.TryGetHeaderName.cs as well. - - public const string Accept = "Accept"; - public const string AcceptCharset = "Accept-Charset"; - public const string AcceptEncoding = "Accept-Encoding"; - public const string AcceptLanguage = "Accept-Language"; - public const string AcceptPatch = "Accept-Patch"; - public const string AcceptRanges = "Accept-Ranges"; - public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; - public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; - public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; - public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; - public const string AccessControlMaxAge = "Access-Control-Max-Age"; - public const string Age = "Age"; - public const string Allow = "Allow"; - public const string AltSvc = "Alt-Svc"; - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string Connection = "Connection"; - public const string ContentDisposition = "Content-Disposition"; - public const string ContentEncoding = "Content-Encoding"; - public const string ContentLanguage = "Content-Language"; - public const string ContentLength = "Content-Length"; - public const string ContentLocation = "Content-Location"; - public const string ContentMD5 = "Content-MD5"; - public const string ContentRange = "Content-Range"; - public const string ContentSecurityPolicy = "Content-Security-Policy"; - public const string ContentType = "Content-Type"; - public const string Cookie = "Cookie"; - public const string Cookie2 = "Cookie2"; - public const string Date = "Date"; - public const string ETag = "ETag"; - public const string Expect = "Expect"; - public const string Expires = "Expires"; - public const string From = "From"; - public const string Host = "Host"; - public const string IfMatch = "If-Match"; - public const string IfModifiedSince = "If-Modified-Since"; - public const string IfNoneMatch = "If-None-Match"; - public const string IfRange = "If-Range"; - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - public const string KeepAlive = "Keep-Alive"; - public const string LastModified = "Last-Modified"; - public const string Link = "Link"; - public const string Location = "Location"; - public const string MaxForwards = "Max-Forwards"; - public const string Origin = "Origin"; - public const string P3P = "P3P"; - public const string Pragma = "Pragma"; - public const string ProxyAuthenticate = "Proxy-Authenticate"; - public const string ProxyAuthorization = "Proxy-Authorization"; - public const string ProxyConnection = "Proxy-Connection"; - public const string PublicKeyPins = "Public-Key-Pins"; - public const string Range = "Range"; - public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. - public const string RetryAfter = "Retry-After"; - public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; - public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; - public const string SecWebSocketKey = "Sec-WebSocket-Key"; - public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; - public const string SecWebSocketVersion = "Sec-WebSocket-Version"; - public const string Server = "Server"; - public const string SetCookie = "Set-Cookie"; - public const string SetCookie2 = "Set-Cookie2"; - public const string StrictTransportSecurity = "Strict-Transport-Security"; - public const string TE = "TE"; - public const string TSV = "TSV"; - public const string Trailer = "Trailer"; - public const string TransferEncoding = "Transfer-Encoding"; - public const string Upgrade = "Upgrade"; - public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; - public const string UserAgent = "User-Agent"; - public const string Vary = "Vary"; - public const string Via = "Via"; - public const string WWWAuthenticate = "WWW-Authenticate"; - public const string Warning = "Warning"; - public const string XAspNetVersion = "X-AspNet-Version"; - public const string XContentDuration = "X-Content-Duration"; - public const string XContentTypeOptions = "X-Content-Type-Options"; - public const string XFrameOptions = "X-Frame-Options"; - public const string XMSEdgeRef = "X-MSEdge-Ref"; - public const string XPoweredBy = "X-Powered-By"; - public const string XRequestID = "X-Request-ID"; - public const string XUACompatible = "X-UA-Compatible"; - } -} diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs deleted file mode 100644 index f17036a21a..0000000000 --- a/SocketHttpListener/Net/HttpListener.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - public sealed class HttpListener : IDisposable - { - internal ICryptoProvider CryptoProvider { get; private set; } - internal ISocketFactory SocketFactory { get; private set; } - internal IFileSystem FileSystem { get; private set; } - internal IStreamHelper StreamHelper { get; private set; } - internal IEnvironmentInfo EnvironmentInfo { get; private set; } - - public bool EnableDualMode { get; set; } - - private AuthenticationSchemes auth_schemes; - private HttpListenerPrefixCollection prefixes; - private AuthenticationSchemeSelector auth_selector; - private string realm; - private bool unsafe_ntlm_auth; - private bool listening; - private bool disposed; - - private Dictionary registry; - private Dictionary connections; - private ILogger _logger; - private X509Certificate _certificate; - - public Action OnContext { get; set; } - - public HttpListener( - ILogger logger, - ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, - IStreamHelper streamHelper, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) - { - _logger = logger; - CryptoProvider = cryptoProvider; - SocketFactory = socketFactory; - StreamHelper = streamHelper; - FileSystem = fileSystem; - EnvironmentInfo = environmentInfo; - - prefixes = new HttpListenerPrefixCollection(logger, this); - registry = new Dictionary(); - connections = new Dictionary(); - auth_schemes = AuthenticationSchemes.Anonymous; - } - - public HttpListener( - ILogger logger, - X509Certificate certificate, - ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, - IStreamHelper streamHelper, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) - : this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo) - { - _certificate = certificate; - } - - public void LoadCert(X509Certificate cert) - { - _certificate = cert; - } - - // TODO: Digest, NTLM and Negotiate require ControlPrincipal - public AuthenticationSchemes AuthenticationSchemes - { - get => auth_schemes; - set - { - CheckDisposed(); - auth_schemes = value; - } - } - - public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate - { - get => auth_selector; - set - { - CheckDisposed(); - auth_selector = value; - } - } - - public bool IsListening => listening; - - public static bool IsSupported => true; - - public HttpListenerPrefixCollection Prefixes - { - get - { - CheckDisposed(); - return prefixes; - } - } - - // TODO: use this - public string Realm - { - get => realm; - set - { - CheckDisposed(); - realm = value; - } - } - - public bool UnsafeConnectionNtlmAuthentication - { - get => unsafe_ntlm_auth; - set - { - CheckDisposed(); - unsafe_ntlm_auth = value; - } - } - - //internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback) - //{ - // lock (registry) - // { - // if (tlsProvider == null) - // tlsProvider = MonoTlsProviderFactory.GetProviderInternal(); - // if (tlsSettings == null) - // tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings(); - // if (tlsSettings.RemoteCertificateValidationCallback == null) - // tlsSettings.RemoteCertificateValidationCallback = callback; - // return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings); - // } - //} - - internal X509Certificate Certificate => _certificate; - - public void Abort() - { - if (disposed) - return; - - if (!listening) - { - return; - } - - Close(true); - } - - public void Close() - { - if (disposed) - return; - - if (!listening) - { - disposed = true; - return; - } - - Close(true); - disposed = true; - } - - void Close(bool force) - { - CheckDisposed(); - HttpEndPointManager.RemoveListener(_logger, this); - Cleanup(force); - } - - void Cleanup(bool close_existing) - { - lock (registry) - { - if (close_existing) - { - // Need to copy this since closing will call UnregisterContext - ICollection keys = registry.Keys; - var all = new HttpListenerContext[keys.Count]; - keys.CopyTo(all, 0); - registry.Clear(); - for (int i = all.Length - 1; i >= 0; i--) - all[i].Connection.Close(true); - } - - lock (connections) - { - ICollection keys = connections.Keys; - var conns = new HttpConnection[keys.Count]; - keys.CopyTo(conns, 0); - connections.Clear(); - for (int i = conns.Length - 1; i >= 0; i--) - conns[i].Close(true); - } - } - } - - internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context) - { - if (AuthenticationSchemeSelectorDelegate != null) - return AuthenticationSchemeSelectorDelegate(context.Request); - else - return auth_schemes; - } - - public void Start() - { - CheckDisposed(); - if (listening) - return; - - HttpEndPointManager.AddListener(_logger, this); - listening = true; - } - - public void Stop() - { - CheckDisposed(); - listening = false; - Close(false); - } - - void IDisposable.Dispose() - { - if (disposed) - return; - - Close(true); //TODO: Should we force here or not? - disposed = true; - } - - internal void CheckDisposed() - { - if (disposed) - throw new ObjectDisposedException(GetType().Name); - } - - internal void RegisterContext(HttpListenerContext context) - { - if (OnContext != null && IsListening) - { - OnContext(context); - } - - lock (registry) - registry[context] = context; - } - - internal void UnregisterContext(HttpListenerContext context) - { - lock (registry) - registry.Remove(context); - } - - internal void AddConnection(HttpConnection cnc) - { - lock (connections) - { - connections[cnc] = cnc; - } - } - - internal void RemoveConnection(HttpConnection cnc) - { - lock (connections) - { - connections.Remove(cnc); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerBasicIdentity.cs b/SocketHttpListener/Net/HttpListenerBasicIdentity.cs deleted file mode 100644 index 5f6ec44b96..0000000000 --- a/SocketHttpListener/Net/HttpListenerBasicIdentity.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Security.Principal; - -namespace SocketHttpListener.Net -{ - public class HttpListenerBasicIdentity : GenericIdentity - { - string password; - - public HttpListenerBasicIdentity(string username, string password) - : base(username, "Basic") - { - this.password = password; - } - - public virtual string Password => password; - } - - public class GenericIdentity : IIdentity - { - private string m_name; - private string m_type; - - public GenericIdentity(string name) - { - if (name == null) - throw new System.ArgumentNullException(nameof(name)); - - m_name = name; - m_type = ""; - } - - public GenericIdentity(string name, string type) - { - if (name == null) - throw new System.ArgumentNullException(nameof(name)); - if (type == null) - throw new System.ArgumentNullException(nameof(type)); - - m_name = name; - m_type = type; - } - - public virtual string Name => m_name; - - public virtual string AuthenticationType => m_type; - - public virtual bool IsAuthenticated => !m_name.Equals(""); - } -} diff --git a/SocketHttpListener/Net/HttpListenerContext.Managed.cs b/SocketHttpListener/Net/HttpListenerContext.Managed.cs deleted file mode 100644 index 79742bf37f..0000000000 --- a/SocketHttpListener/Net/HttpListenerContext.Managed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.ComponentModel; -using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerContext - { - private HttpConnection _connection; - - internal HttpListenerContext(HttpConnection connection) - { - _connection = connection; - _response = new HttpListenerResponse(this); - Request = new HttpListenerRequest(this); - ErrorStatus = 400; - } - - internal int ErrorStatus { get; set; } - - internal string ErrorMessage { get; set; } - - internal bool HaveError => ErrorMessage != null; - - internal HttpConnection Connection => _connection; - - internal void ParseAuthentication(System.Net.AuthenticationSchemes expectedSchemes) - { - if (expectedSchemes == System.Net.AuthenticationSchemes.Anonymous) - return; - - string header = Request.Headers["Authorization"]; - if (string.IsNullOrEmpty(header)) - return; - - if (IsBasicHeader(header)) - { - _user = ParseBasicAuthentication(header.Substring(AuthenticationTypes.Basic.Length + 1)); - } - } - - internal IPrincipal ParseBasicAuthentication(string authData) => - TryParseBasicAuth(authData, out HttpStatusCode errorCode, out string username, out string password) ? - new GenericPrincipal(new HttpListenerBasicIdentity(username, password), Array.Empty()) : - null; - - internal static bool IsBasicHeader(string header) => - header.Length >= 6 && - header[5] == ' ' && - string.Compare(header, 0, AuthenticationTypes.Basic, 0, 5, StringComparison.OrdinalIgnoreCase) == 0; - - internal static bool TryParseBasicAuth(string headerValue, out HttpStatusCode errorCode, out string username, out string password) - { - errorCode = HttpStatusCode.OK; - username = password = null; - try - { - if (string.IsNullOrWhiteSpace(headerValue)) - { - return false; - } - - string authString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); - int colonPos = authString.IndexOf(':'); - if (colonPos < 0) - { - // username must be at least 1 char - errorCode = HttpStatusCode.BadRequest; - return false; - } - - username = authString.Substring(0, colonPos); - password = authString.Substring(colonPos + 1); - return true; - } - catch - { - errorCode = HttpStatusCode.InternalServerError; - return false; - } - } - - public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) - { - return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment internalBuffer) - { - WebSocketValidate.ValidateArraySegment(internalBuffer, nameof(internalBuffer)); - HttpWebSocket.ValidateOptions(subProtocol, receiveBufferSize, HttpWebSocket.MinSendBufferSize, keepAliveInterval); - return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerContext.cs b/SocketHttpListener/Net/HttpListenerContext.cs deleted file mode 100644 index d84e2d1aa0..0000000000 --- a/SocketHttpListener/Net/HttpListenerContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Net; -using System.Security.Principal; -using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerContext - { - private HttpListenerResponse _response; - private IPrincipal _user; - - public HttpListenerRequest Request { get; } - - public IPrincipal User => _user; - - // This can be used to cache the results of HttpListener.AuthenticationSchemeSelectorDelegate. - internal AuthenticationSchemes AuthenticationSchemes { get; set; } - - public HttpListenerResponse Response => _response; - - public Task AcceptWebSocketAsync(string subProtocol) - { - return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, WebSocket.DefaultKeepAliveInterval); - } - - public Task AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval) - { - return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, keepAliveInterval); - } - } - - public class GenericPrincipal : IPrincipal - { - private IIdentity m_identity; - private string[] m_roles; - - public GenericPrincipal(IIdentity identity, string[] roles) - { - if (identity == null) - throw new ArgumentNullException(nameof(identity)); - - m_identity = identity; - if (roles != null) - { - m_roles = new string[roles.Length]; - for (int i = 0; i < roles.Length; ++i) - { - m_roles[i] = roles[i]; - } - } - else - { - m_roles = null; - } - } - - public virtual IIdentity Identity => m_identity; - - public virtual bool IsInRole(string role) - { - if (role == null || m_roles == null) - return false; - - for (int i = 0; i < m_roles.Length; ++i) - { - if (m_roles[i] != null && string.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0) - return true; - } - return false; - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs deleted file mode 100644 index 400a1adb61..0000000000 --- a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable - { - private List _prefixes = new List(); - private HttpListener _listener; - - private ILogger _logger; - - internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener) - { - _logger = logger; - _listener = listener; - } - - public int Count => _prefixes.Count; - - public bool IsReadOnly => false; - - public bool IsSynchronized => false; - - public void Add(string uriPrefix) - { - _listener.CheckDisposed(); - //ListenerPrefix.CheckUri(uriPrefix); - if (_prefixes.Contains(uriPrefix)) - { - return; - } - - _prefixes.Add(uriPrefix); - if (_listener.IsListening) - { - HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); - } - } - - public void AddRange(IEnumerable uriPrefixes) - { - _listener.CheckDisposed(); - - foreach (var uriPrefix in uriPrefixes) - { - if (_prefixes.Contains(uriPrefix)) - { - continue; - } - - _prefixes.Add(uriPrefix); - if (_listener.IsListening) - { - HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); - } - } - } - - public void Clear() - { - _listener.CheckDisposed(); - _prefixes.Clear(); - if (_listener.IsListening) - { - HttpEndPointManager.RemoveListener(_logger, _listener); - } - } - - public bool Contains(string uriPrefix) - { - _listener.CheckDisposed(); - return _prefixes.Contains(uriPrefix); - } - - public void CopyTo(string[] array, int offset) - { - _listener.CheckDisposed(); - _prefixes.CopyTo(array, offset); - } - - public void CopyTo(Array array, int offset) - { - _listener.CheckDisposed(); - ((ICollection)_prefixes).CopyTo(array, offset); - } - - public IEnumerator GetEnumerator() - { - return _prefixes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _prefixes.GetEnumerator(); - } - - public bool Remove(string uriPrefix) - { - _listener.CheckDisposed(); - if (uriPrefix == null) - { - throw new ArgumentNullException(nameof(uriPrefix)); - } - - bool result = _prefixes.Remove(uriPrefix); - if (result && _listener.IsListening) - { - HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener); - } - - return result; - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs b/SocketHttpListener/Net/HttpListenerRequest.Managed.cs deleted file mode 100644 index 3f9e32f089..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.IO; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerRequest - { - private long _contentLength; - private bool _clSet; - private WebHeaderCollection _headers; - private string _method; - private Stream _inputStream; - private HttpListenerContext _context; - private bool _isChunked; - - private static byte[] s_100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); - - internal HttpListenerRequest(HttpListenerContext context) - { - _context = context; - _headers = new WebHeaderCollection(); - _version = HttpVersion.Version10; - } - - private static readonly char[] s_separators = new char[] { ' ' }; - - internal void SetRequestLine(string req) - { - string[] parts = req.Split(s_separators, 3); - if (parts.Length != 3) - { - _context.ErrorMessage = "Invalid request line (parts)."; - return; - } - - _method = parts[0]; - foreach (char c in _method) - { - int ic = (int)c; - - if ((ic >= 'A' && ic <= 'Z') || - (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && - c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && - c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && - c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) - continue; - - _context.ErrorMessage = "(Invalid verb)"; - return; - } - - _rawUrl = parts[1]; - if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/")) - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - - try - { - _version = new Version(parts[2].Substring(5)); - } - catch - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - - if (_version.Major < 1) - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - if (_version.Major > 1) - { - _context.ErrorStatus = (int)HttpStatusCode.HttpVersionNotSupported; - _context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.HttpVersionNotSupported); - return; - } - } - - private static bool MaybeUri(string s) - { - int p = s.IndexOf(':'); - if (p == -1) - return false; - - if (p >= 10) - return false; - - return IsPredefinedScheme(s.Substring(0, p)); - } - - private static bool IsPredefinedScheme(string scheme) - { - if (scheme == null || scheme.Length < 3) - return false; - - char c = scheme[0]; - if (c == 'h') - return (scheme == UriScheme.Http || scheme == UriScheme.Https); - if (c == 'f') - return (scheme == UriScheme.File || scheme == UriScheme.Ftp); - - if (c == 'n') - { - c = scheme[1]; - if (c == 'e') - return (scheme == UriScheme.News || scheme == UriScheme.NetPipe || scheme == UriScheme.NetTcp); - if (scheme == UriScheme.Nntp) - return true; - return false; - } - if ((c == 'g' && scheme == UriScheme.Gopher) || (c == 'm' && scheme == UriScheme.Mailto)) - return true; - - return false; - } - - internal void FinishInitialization() - { - string host = UserHostName; - if (_version > HttpVersion.Version10 && (host == null || host.Length == 0)) - { - _context.ErrorMessage = "Invalid host name"; - return; - } - - string path; - Uri raw_uri = null; - if (MaybeUri(_rawUrl.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri)) - path = raw_uri.PathAndQuery; - else - path = _rawUrl; - - if ((host == null || host.Length == 0)) - host = UserHostAddress; - - if (raw_uri != null) - host = raw_uri.Host; - - int colon = host.IndexOf(']') == -1 ? host.IndexOf(':') : host.LastIndexOf(':'); - if (colon >= 0) - host = host.Substring(0, colon); - - string base_uri = string.Format("{0}://{1}:{2}", RequestScheme, host, LocalEndPoint.Port); - - if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri)) - { - _context.ErrorMessage = System.Net.WebUtility.HtmlEncode("Invalid url: " + base_uri + path); - return; - } - - _requestUri = HttpListenerRequestUriBuilder.GetRequestUri(_rawUrl, _requestUri.Scheme, - _requestUri.Authority, _requestUri.LocalPath, _requestUri.Query); - - if (_version >= HttpVersion.Version11) - { - string t_encoding = Headers[HttpKnownHeaderNames.TransferEncoding]; - _isChunked = (t_encoding != null && string.Equals(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase)); - // 'identity' is not valid! - if (t_encoding != null && !_isChunked) - { - _context.Connection.SendError(null, 501); - return; - } - } - - if (!_isChunked && !_clSet) - { - if (string.Equals(_method, "POST", StringComparison.OrdinalIgnoreCase) || - string.Equals(_method, "PUT", StringComparison.OrdinalIgnoreCase)) - { - _context.Connection.SendError(null, 411); - return; - } - } - - if (string.Compare(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) - { - HttpResponseStream output = _context.Connection.GetResponseStream(); - output.InternalWrite(s_100continue, 0, s_100continue.Length); - } - } - - internal static string Unquote(string str) - { - int start = str.IndexOf('\"'); - int end = str.LastIndexOf('\"'); - if (start >= 0 && end >= 0) - str = str.Substring(start + 1, end - 1); - return str.Trim(); - } - - internal void AddHeader(string header) - { - int colon = header.IndexOf(':'); - if (colon == -1 || colon == 0) - { - _context.ErrorMessage = HttpStatusDescription.Get(400); - _context.ErrorStatus = 400; - return; - } - - string name = header.Substring(0, colon).Trim(); - string val = header.Substring(colon + 1).Trim(); - if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase)) - { - // To match Windows behavior: - // Content lengths >= 0 and <= long.MaxValue are accepted as is. - // Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0. - // Content lengths < 0 cause the requests to fail. - // Other input is a failure, too. - long parsedContentLength = - ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) : - long.Parse(val); - if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength)) - { - _context.ErrorMessage = "Invalid Content-Length."; - } - else - { - _contentLength = parsedContentLength; - _clSet = true; - } - } - else if (name.Equals("transfer-encoding", StringComparison.OrdinalIgnoreCase)) - { - if (Headers[HttpKnownHeaderNames.TransferEncoding] != null) - { - _context.ErrorStatus = (int)HttpStatusCode.NotImplemented; - _context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.NotImplemented); - } - } - - if (_context.ErrorMessage == null) - { - _headers.Set(name, val); - } - } - - // returns true is the stream could be reused. - internal bool FlushInput() - { - if (!HasEntityBody) - return true; - - int length = 2048; - if (_contentLength > 0) - length = (int)Math.Min(_contentLength, (long)length); - - byte[] bytes = new byte[length]; - while (true) - { - try - { - IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null); - if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000)) - return false; - if (InputStream.EndRead(ares) <= 0) - return true; - } - catch (ObjectDisposedException) - { - _inputStream = null; - return true; - } - catch - { - return false; - } - } - } - - public long ContentLength64 - { - get - { - if (_isChunked) - _contentLength = -1; - - return _contentLength; - } - } - - public bool HasEntityBody => (_contentLength > 0 || _isChunked); - - public QueryParamCollection Headers => _headers; - - public string HttpMethod => _method; - - public Stream InputStream - { - get - { - if (_inputStream == null) - { - if (_isChunked || _contentLength > 0) - _inputStream = _context.Connection.GetRequestStream(_isChunked, _contentLength); - else - _inputStream = Stream.Null; - } - - return _inputStream; - } - } - - public bool IsAuthenticated => false; - - public bool IsSecureConnection => _context.Connection.IsSecure; - - public System.Net.IPEndPoint LocalEndPoint => _context.Connection.LocalEndPoint; - - public System.Net.IPEndPoint RemoteEndPoint => _context.Connection.RemoteEndPoint; - - public Guid RequestTraceIdentifier { get; } = Guid.NewGuid(); - - public string ServiceName => null; - - private Uri RequestUri => _requestUri; - private bool SupportsWebSockets => true; - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequest.cs b/SocketHttpListener/Net/HttpListenerRequest.cs deleted file mode 100644 index 667d58ea7b..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequest.cs +++ /dev/null @@ -1,537 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerRequest - { - private CookieCollection _cookies; - private bool? _keepAlive; - private string _rawUrl; - private Uri _requestUri; - private Version _version; - - public string[] AcceptTypes => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.Accept]); - - public string[] UserLanguages => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.AcceptLanguage]); - - private static CookieCollection ParseCookies(Uri uri, string setCookieHeader) - { - var cookies = new CookieCollection(); - return cookies; - } - - public CookieCollection Cookies - { - get - { - if (_cookies == null) - { - string cookieString = Headers[HttpKnownHeaderNames.Cookie]; - if (!string.IsNullOrEmpty(cookieString)) - { - _cookies = ParseCookies(RequestUri, cookieString); - } - if (_cookies == null) - { - _cookies = new CookieCollection(); - } - } - return _cookies; - } - } - - public Encoding ContentEncoding - { - get - { - if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) - { - string postDataCharset = Headers["x-up-devcap-post-charset"]; - if (postDataCharset != null && postDataCharset.Length > 0) - { - try - { - return Encoding.GetEncoding(postDataCharset); - } - catch (ArgumentException) - { - } - } - } - if (HasEntityBody) - { - if (ContentType != null) - { - string charSet = Helpers.GetCharSetValueFromHeader(ContentType); - if (charSet != null) - { - try - { - return Encoding.GetEncoding(charSet); - } - catch (ArgumentException) - { - } - } - } - } - return Encoding.UTF8; - } - } - - public string ContentType => Headers[HttpKnownHeaderNames.ContentType]; - - public bool IsLocal => LocalEndPoint.Address.Equals(RemoteEndPoint.Address); - - public bool IsWebSocketRequest - { - get - { - if (!SupportsWebSockets) - { - return false; - } - - bool foundConnectionUpgradeHeader = false; - if (string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Connection]) || string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Upgrade])) - { - return false; - } - - foreach (string connection in Headers.GetValues(HttpKnownHeaderNames.Connection)) - { - if (string.Equals(connection, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase)) - { - foundConnectionUpgradeHeader = true; - break; - } - } - - if (!foundConnectionUpgradeHeader) - { - return false; - } - - foreach (string upgrade in Headers.GetValues(HttpKnownHeaderNames.Upgrade)) - { - if (string.Equals(upgrade, HttpWebSocket.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - } - - public bool KeepAlive - { - get - { - if (!_keepAlive.HasValue) - { - string header = Headers[HttpKnownHeaderNames.ProxyConnection]; - if (string.IsNullOrEmpty(header)) - { - header = Headers[HttpKnownHeaderNames.Connection]; - } - if (string.IsNullOrEmpty(header)) - { - if (ProtocolVersion >= HttpVersion.Version11) - { - _keepAlive = true; - } - else - { - header = Headers[HttpKnownHeaderNames.KeepAlive]; - _keepAlive = !string.IsNullOrEmpty(header); - } - } - else - { - header = header.ToLowerInvariant(); - _keepAlive = - header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 || - header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0; - } - } - - return _keepAlive.Value; - } - } - - public QueryParamCollection QueryString - { - get - { - var queryString = new QueryParamCollection(); - Helpers.FillFromString(queryString, Url.Query, true, ContentEncoding); - return queryString; - } - } - - public string RawUrl => _rawUrl; - - private string RequestScheme => IsSecureConnection ? UriScheme.Https : UriScheme.Http; - - public string UserAgent => Headers[HttpKnownHeaderNames.UserAgent]; - - public string UserHostAddress => LocalEndPoint.ToString(); - - public string UserHostName => Headers[HttpKnownHeaderNames.Host]; - - public Uri UrlReferrer - { - get - { - string referrer = Headers[HttpKnownHeaderNames.Referer]; - if (referrer == null) - { - return null; - } - - bool success = Uri.TryCreate(referrer, UriKind.RelativeOrAbsolute, out var urlReferrer); - return success ? urlReferrer : null; - } - } - - public Uri Url => RequestUri; - - public Version ProtocolVersion => _version; - - private static class Helpers - { - // - // Get attribute off header value - // - internal static string GetCharSetValueFromHeader(string headerValue) - { - const string AttrName = "charset"; - - if (headerValue == null) - return null; - - int l = headerValue.Length; - int k = AttrName.Length; - - // find properly separated attribute name - int i = 1; // start searching from 1 - - while (i < l) - { - i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(headerValue, AttrName, i, CompareOptions.IgnoreCase); - if (i < 0) - break; - if (i + k >= l) - break; - - char chPrev = headerValue[i - 1]; - char chNext = headerValue[i + k]; - if ((chPrev == ';' || chPrev == ',' || char.IsWhiteSpace(chPrev)) && (chNext == '=' || char.IsWhiteSpace(chNext))) - break; - - i += k; - } - - if (i < 0 || i >= l) - return null; - - // skip to '=' and the following whitespace - i += k; - while (i < l && char.IsWhiteSpace(headerValue[i])) - i++; - if (i >= l || headerValue[i] != '=') - return null; - i++; - while (i < l && char.IsWhiteSpace(headerValue[i])) - i++; - if (i >= l) - return null; - - // parse the value - string attrValue = null; - - int j; - - if (i < l && headerValue[i] == '"') - { - if (i == l - 1) - return null; - j = headerValue.IndexOf('"', i + 1); - if (j < 0 || j == i + 1) - return null; - - attrValue = headerValue.Substring(i + 1, j - i - 1).Trim(); - } - else - { - for (j = i; j < l; j++) - { - if (headerValue[j] == ';') - break; - } - - if (j == i) - return null; - - attrValue = headerValue.Substring(i, j - i).Trim(); - } - - return attrValue; - } - - internal static string[] ParseMultivalueHeader(string s) - { - if (s == null) - return null; - - int l = s.Length; - - // collect comma-separated values into list - - var values = new List(); - int i = 0; - - while (i < l) - { - // find next , - int ci = s.IndexOf(',', i); - if (ci < 0) - ci = l; - - // append corresponding server value - values.Add(s.Substring(i, ci - i)); - - // move to next - i = ci + 1; - - // skip leading space - if (i < l && s[i] == ' ') - i++; - } - - // return list as array of strings - - int n = values.Count; - string[] strings; - - // if n is 0 that means s was empty string - - if (n == 0) - { - strings = new string[1]; - strings[0] = string.Empty; - } - else - { - strings = new string[n]; - values.CopyTo(0, strings, 0, n); - } - return strings; - } - - - private static string UrlDecodeStringFromStringInternal(string s, Encoding e) - { - int count = s.Length; - var helper = new UrlDecoder(count, e); - - // go through the string's chars collapsing %XX and %uXXXX and - // appending each char as char, with exception of %XX constructs - // that are appended as bytes - - for (int pos = 0; pos < count; pos++) - { - char ch = s[pos]; - - if (ch == '+') - { - ch = ' '; - } - else if (ch == '%' && pos < count - 2) - { - if (s[pos + 1] == 'u' && pos < count - 5) - { - int h1 = HexToInt(s[pos + 2]); - int h2 = HexToInt(s[pos + 3]); - int h3 = HexToInt(s[pos + 4]); - int h4 = HexToInt(s[pos + 5]); - - if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) - { // valid 4 hex chars - ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); - pos += 5; - - // only add as char - helper.AddChar(ch); - continue; - } - } - else - { - int h1 = HexToInt(s[pos + 1]); - int h2 = HexToInt(s[pos + 2]); - - if (h1 >= 0 && h2 >= 0) - { // valid 2 hex chars - byte b = (byte)((h1 << 4) | h2); - pos += 2; - - // don't add as char - helper.AddByte(b); - continue; - } - } - } - - if ((ch & 0xFF80) == 0) - helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode - else - helper.AddChar(ch); - } - - return helper.GetString(); - } - - private static int HexToInt(char h) - { - return (h >= '0' && h <= '9') ? h - '0' : - (h >= 'a' && h <= 'f') ? h - 'a' + 10 : - (h >= 'A' && h <= 'F') ? h - 'A' + 10 : - -1; - } - - private class UrlDecoder - { - private int _bufferSize; - - // Accumulate characters in a special array - private int _numChars; - private char[] _charBuffer; - - // Accumulate bytes for decoding into characters in a special array - private int _numBytes; - private byte[] _byteBuffer; - - // Encoding to convert chars to bytes - private Encoding _encoding; - - private void FlushBytes() - { - if (_numBytes > 0) - { - _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); - _numBytes = 0; - } - } - - internal UrlDecoder(int bufferSize, Encoding encoding) - { - _bufferSize = bufferSize; - _encoding = encoding; - - _charBuffer = new char[bufferSize]; - // byte buffer created on demand - } - - internal void AddChar(char ch) - { - if (_numBytes > 0) - FlushBytes(); - - _charBuffer[_numChars++] = ch; - } - - internal void AddByte(byte b) - { - { - if (_byteBuffer == null) - _byteBuffer = new byte[_bufferSize]; - - _byteBuffer[_numBytes++] = b; - } - } - - internal string GetString() - { - if (_numBytes > 0) - FlushBytes(); - - if (_numChars > 0) - return new string(_charBuffer, 0, _numChars); - else - return string.Empty; - } - } - - - internal static void FillFromString(QueryParamCollection nvc, string s, bool urlencoded, Encoding encoding) - { - int l = (s != null) ? s.Length : 0; - int i = (s.Length > 0 && s[0] == '?') ? 1 : 0; - - while (i < l) - { - // find next & while noting first = on the way (and if there are more) - - int si = i; - int ti = -1; - - while (i < l) - { - char ch = s[i]; - - if (ch == '=') - { - if (ti < 0) - ti = i; - } - else if (ch == '&') - { - break; - } - - i++; - } - - // extract the name / value pair - - string name = null; - string value = null; - - if (ti >= 0) - { - name = s.Substring(si, ti - si); - value = s.Substring(ti + 1, i - ti - 1); - } - else - { - value = s.Substring(si, i - si); - } - - // add name / value pair to the collection - - if (urlencoded) - nvc.Add( - name == null ? null : UrlDecodeStringFromStringInternal(name, encoding), - UrlDecodeStringFromStringInternal(value, encoding)); - else - nvc.Add(name, value); - - // trailing '&' - - if (i == l - 1 && s[i] == '&') - nvc.Add(null, ""); - - i++; - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs b/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs deleted file mode 100644 index 7b4b619e68..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs +++ /dev/null @@ -1,443 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Text; - -namespace SocketHttpListener.Net -{ - // We don't use the cooked URL because http.sys unescapes all percent-encoded values. However, - // we also can't just use the raw Uri, since http.sys supports not only Utf-8, but also ANSI/DBCS and - // Unicode code points. System.Uri only supports Utf-8. - // The purpose of this class is to convert all ANSI, DBCS, and Unicode code points into percent encoded - // Utf-8 characters. - internal sealed class HttpListenerRequestUriBuilder - { - private static readonly Encoding s_utf8Encoding = new UTF8Encoding(false, true); - private static readonly Encoding s_ansiEncoding = Encoding.GetEncoding(0, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - - private readonly string _rawUri; - private readonly string _cookedUriScheme; - private readonly string _cookedUriHost; - private readonly string _cookedUriPath; - private readonly string _cookedUriQuery; - - // This field is used to build the final request Uri string from the Uri parts passed to the ctor. - private StringBuilder _requestUriString; - - // The raw path is parsed by looping through all characters from left to right. 'rawOctets' - // is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%2F/ - // rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x2F } when - // we reach the final '/'. I.e. after a sequence of percent encoded octets ends, we use rawOctets as - // input to the encoding and percent encode the resulting string into UTF-8 octets. - // - // When parsing ANSI (Latin 1) encoded path '/pa%C4th/', %C4 will be added to rawOctets and when - // we reach 't', the content of rawOctets { 0xC4 } will be fed into the ANSI encoding. The resulting - // string 'Ä' will be percent encoded into UTF-8 octets and appended to requestUriString. The final - // path will be '/pa%C3%84th/', where '%C3%84' is the UTF-8 percent encoded character 'Ä'. - private List _rawOctets; - private string _rawPath; - - // Holds the final request Uri. - private Uri _requestUri; - - private HttpListenerRequestUriBuilder(string rawUri, string cookedUriScheme, string cookedUriHost, - string cookedUriPath, string cookedUriQuery) - { - _rawUri = rawUri; - _cookedUriScheme = cookedUriScheme; - _cookedUriHost = cookedUriHost; - _cookedUriPath = AddSlashToAsteriskOnlyPath(cookedUriPath); - _cookedUriQuery = cookedUriQuery ?? string.Empty; - } - - public static Uri GetRequestUri(string rawUri, string cookedUriScheme, string cookedUriHost, - string cookedUriPath, string cookedUriQuery) - { - var builder = new HttpListenerRequestUriBuilder(rawUri, - cookedUriScheme, cookedUriHost, cookedUriPath, cookedUriQuery); - - return builder.Build(); - } - - private Uri Build() - { - BuildRequestUriUsingRawPath(); - - if (_requestUri == null) - { - BuildRequestUriUsingCookedPath(); - } - - return _requestUri; - } - - private void BuildRequestUriUsingCookedPath() - { - bool isValid = Uri.TryCreate(_cookedUriScheme + Uri.SchemeDelimiter + _cookedUriHost + _cookedUriPath + - _cookedUriQuery, UriKind.Absolute, out _requestUri); - - // Creating a Uri from the cooked Uri should really always work: If not, we log at least. - if (!isValid) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _cookedUriPath, _cookedUriQuery)); - } - } - - private void BuildRequestUriUsingRawPath() - { - bool isValid = false; - - // Initialize 'rawPath' only if really needed; i.e. if we build the request Uri from the raw Uri. - _rawPath = GetPath(_rawUri); - - // Try to check the raw path using first the primary encoding (according to http.sys settings); - // if it fails try the secondary encoding. - ParsingResult result = BuildRequestUriUsingRawPath(GetEncoding(EncodingType.Primary)); - if (result == ParsingResult.EncodingError) - { - Encoding secondaryEncoding = GetEncoding(EncodingType.Secondary); - result = BuildRequestUriUsingRawPath(secondaryEncoding); - } - isValid = (result == ParsingResult.Success) ? true : false; - - // Log that we weren't able to create a Uri from the raw string. - if (!isValid) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _rawPath, _cookedUriQuery)); - } - } - - private static Encoding GetEncoding(EncodingType type) - { - Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary), - "Unknown 'EncodingType' value: " + type.ToString()); - - if (type == EncodingType.Secondary) - { - return s_ansiEncoding; - } - else - { - return s_utf8Encoding; - } - } - - private ParsingResult BuildRequestUriUsingRawPath(Encoding encoding) - { - Debug.Assert(encoding != null, "'encoding' must be assigned."); - Debug.Assert(!string.IsNullOrEmpty(_rawPath), "'rawPath' must have at least one character."); - - _rawOctets = new List(); - _requestUriString = new StringBuilder(); - _requestUriString.Append(_cookedUriScheme); - _requestUriString.Append(Uri.SchemeDelimiter); - _requestUriString.Append(_cookedUriHost); - - ParsingResult result = ParseRawPath(encoding); - if (result == ParsingResult.Success) - { - _requestUriString.Append(_cookedUriQuery); - - Debug.Assert(_rawOctets.Count == 0, - "Still raw octets left. They must be added to the result path."); - - if (!Uri.TryCreate(_requestUriString.ToString(), UriKind.Absolute, out _requestUri)) - { - // If we can't create a Uri from the string, this is an invalid string and it doesn't make - // sense to try another encoding. - result = ParsingResult.InvalidString; - } - } - - if (result != ParsingResult.Success) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_raw_path, _rawPath, encoding.EncodingName)); - } - - return result; - } - - private ParsingResult ParseRawPath(Encoding encoding) - { - Debug.Assert(encoding != null, "'encoding' must be assigned."); - - int index = 0; - char current = '\0'; - while (index < _rawPath.Length) - { - current = _rawPath[index]; - if (current == '%') - { - // Assert is enough, since http.sys accepted the request string already. This should never happen. - Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %2F)"); - - index++; - current = _rawPath[index]; - if (current == 'u' || current == 'U') - { - // We found "%u" which means, we have a Unicode code point of the form "%uXXXX". - Debug.Assert(index + 4 < _rawPath.Length, "Expected >=4 characters after '%u' (e.g. %u0062)"); - - // Decode the content of rawOctets into percent encoded UTF-8 characters and append them - // to requestUriString. - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - if (!AppendUnicodeCodePointValuePercentEncoded(_rawPath.Substring(index + 1, 4))) - { - return ParsingResult.InvalidString; - } - index += 5; - } - else - { - // We found '%', but not followed by 'u', i.e. we have a percent encoded octed: %XX - if (!AddPercentEncodedOctetToRawOctetsList(encoding, _rawPath.Substring(index, 2))) - { - return ParsingResult.InvalidString; - } - index += 2; - } - } - else - { - // We found a non-'%' character: decode the content of rawOctets into percent encoded - // UTF-8 characters and append it to the result. - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - // Append the current character to the result. - _requestUriString.Append(current); - index++; - } - } - - // if the raw path ends with a sequence of percent encoded octets, make sure those get added to the - // result (requestUriString). - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - - return ParsingResult.Success; - } - - private bool AppendUnicodeCodePointValuePercentEncoded(string codePoint) - { - // http.sys only supports %uXXXX (4 hex-digits), even though unicode code points could have up to - // 6 hex digits. Therefore we parse always 4 characters after %u and convert them to an int. - if (!int.TryParse(codePoint, NumberStyles.HexNumber, null, out var codePointValue)) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint)); - return false; - } - - string unicodeString = null; - try - { - unicodeString = char.ConvertFromUtf32(codePointValue); - AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(unicodeString)); - - return true; - } - catch (ArgumentOutOfRangeException) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint)); - } - catch (EncoderFallbackException) - { - // If utf8Encoding.GetBytes() fails - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, unicodeString, e.Message)); - } - - return false; - } - - private bool AddPercentEncodedOctetToRawOctetsList(Encoding encoding, string escapedCharacter) - { - if (!byte.TryParse(escapedCharacter, NumberStyles.HexNumber, null, out byte encodedValue)) - { - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, escapedCharacter)); - return false; - } - - _rawOctets.Add(encodedValue); - - return true; - } - - private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding) - { - if (_rawOctets.Count == 0) - { - return true; - } - - string decodedString = null; - try - { - // If the encoding can get a string out of the byte array, this is a valid string in the - // 'encoding' encoding. - decodedString = encoding.GetString(_rawOctets.ToArray()); - - if (encoding == s_utf8Encoding) - { - AppendOctetsPercentEncoded(_requestUriString, _rawOctets.ToArray()); - } - else - { - AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(decodedString)); - } - - _rawOctets.Clear(); - - return true; - } - catch (DecoderFallbackException) - { - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_bytes, GetOctetsAsString(_rawOctets), e.Message)); - } - catch (EncoderFallbackException) - { - // If utf8Encoding.GetBytes() fails - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, decodedString, e.Message)); - } - - return false; - } - - private static void AppendOctetsPercentEncoded(StringBuilder target, IEnumerable octets) - { - foreach (byte octet in octets) - { - target.Append('%'); - target.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); - } - } - - private static string GetOctetsAsString(IEnumerable octets) - { - var octetString = new StringBuilder(); - - bool first = true; - foreach (byte octet in octets) - { - if (first) - { - first = false; - } - else - { - octetString.Append(' '); - } - octetString.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); - } - - return octetString.ToString(); - } - - private static string GetPath(string uriString) - { - Debug.Assert(uriString != null, "uriString must not be null"); - Debug.Assert(uriString.Length > 0, "uriString must not be empty"); - - int pathStartIndex = 0; - - // Perf. improvement: nearly all strings are relative Uris. So just look if the - // string starts with '/'. If so, we have a relative Uri and the path starts at position 0. - // (http.sys already trimmed leading whitespaces) - if (uriString[0] != '/') - { - // We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to - // use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the - // Uri starts with either http:// or https://. - int authorityStartIndex = 0; - if (uriString.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) - { - authorityStartIndex = 7; - } - else if (uriString.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) - { - authorityStartIndex = 8; - } - - if (authorityStartIndex > 0) - { - // we have an absolute Uri. Find out where the authority ends and the path begins. - // Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616 - // and http.sys behavior: If the Uri contains a query, there must be at least one '/' - // between the authority and the '?' character: It's safe to just look for the first - // '/' after the authority to determine the beginning of the path. - pathStartIndex = uriString.IndexOf('/', authorityStartIndex); - if (pathStartIndex == -1) - { - // e.g. for request lines like: 'GET http://myserver' (no final '/') - pathStartIndex = uriString.Length; - } - } - else - { - // RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority - // 'authority' can only be used with CONNECT which is never received by HttpListener. - // I.e. if we don't have an absolute path (must start with '/') and we don't have - // an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'. - Debug.Assert((uriString.Length == 1) && (uriString[0] == '*'), "Unknown request Uri string format", - "Request Uri string is not an absolute Uri, absolute path, or '*': {0}", uriString); - - // Should we ever get here, be consistent with 2.0/3.5 behavior: just add an initial - // slash to the string and treat it as a path: - uriString = "/" + uriString; - } - } - - // Find end of path: The path is terminated by - // - the first '?' character - // - the first '#' character: This is never the case here, since http.sys won't accept - // Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris. - // - end of Uri string - int queryIndex = uriString.IndexOf('?'); - if (queryIndex == -1) - { - queryIndex = uriString.Length; - } - - // will always return a != null string. - return AddSlashToAsteriskOnlyPath(uriString.Substring(pathStartIndex, queryIndex - pathStartIndex)); - } - - private static string AddSlashToAsteriskOnlyPath(string path) - { - Debug.Assert(path != null, "'path' must not be null"); - - // If a request like "OPTIONS * HTTP/1.1" is sent to the listener, then the request Uri - // should be "http[s]://server[:port]/*" to be compatible with pre-4.0 behavior. - if ((path.Length == 1) && (path[0] == '*')) - { - return "/*"; - } - - return path; - } - - private enum ParsingResult - { - Success, - InvalidString, - EncodingError - } - - private enum EncodingType - { - Primary, - Secondary - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerResponse.Managed.cs b/SocketHttpListener/Net/HttpListenerResponse.Managed.cs deleted file mode 100644 index f595fce7cc..0000000000 --- a/SocketHttpListener/Net/HttpListenerResponse.Managed.cs +++ /dev/null @@ -1,333 +0,0 @@ -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; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerResponse : IDisposable - { - private long _contentLength; - private Version _version = HttpVersion.Version11; - private int _statusCode = 200; - internal object _headersLock = new object(); - private bool _forceCloseChunked; - - internal HttpListenerResponse(HttpListenerContext context) - { - _httpContext = context; - } - - internal bool ForceCloseChunked => _forceCloseChunked; - - private void EnsureResponseStream() - { - if (_responseStream == null) - { - _responseStream = _httpContext.Connection.GetResponseStream(); - } - } - - public Version ProtocolVersion - { - get => _version; - set - { - CheckDisposed(); - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1)) - { - throw new ArgumentException("Wrong version"); - } - - _version = new Version(value.Major, value.Minor); // match Windows behavior, trimming to just Major.Minor - } - } - - public int StatusCode - { - get => _statusCode; - set - { - CheckDisposed(); - - if (value < 100 || value > 999) - throw new ProtocolViolationException("Invalid status"); - - _statusCode = value; - } - } - - private void Dispose() - { - Close(true); - } - - public void Close() - { - if (Disposed) - return; - - Close(false); - } - - public void Abort() - { - if (Disposed) - return; - - Close(true); - } - - private void Close(bool force) - { - Disposed = true; - _httpContext.Connection.Close(force); - } - - public void Close(byte[] responseEntity, bool willBlock) - { - CheckDisposed(); - - if (responseEntity == null) - { - throw new ArgumentNullException(nameof(responseEntity)); - } - - if (!SentHeaders && _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 - { - try - { - thisRef.OutputStream.EndWrite(iar); - } - finally - { - thisRef.Close(false); - } - } - catch (Exception) - { - // In case response was disposed during this time - } - }, this); - } - } - - public void CopyFrom(HttpListenerResponse templateResponse) - { - _webHeaders.Clear(); - //_webHeaders.Add(templateResponse._webHeaders); - _contentLength = templateResponse._contentLength; - _statusCode = templateResponse._statusCode; - _statusDescription = templateResponse._statusDescription; - _keepAlive = templateResponse._keepAlive; - _version = templateResponse._version; - } - - internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandshake = false) - { - if (!isWebSocketHandshake) - { - if (_webHeaders["Server"] == null) - { - _webHeaders.Set("Server", "Microsoft-NetCore/2.0"); - } - - if (_webHeaders["Date"] == null) - { - _webHeaders.Set("Date", DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)); - } - - if (_boundaryType == BoundaryType.None) - { - if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10) - { - _keepAlive = false; - } - else - { - _boundaryType = BoundaryType.Chunked; - } - - if (CanSendResponseBody(_httpContext.Response.StatusCode)) - { - _contentLength = -1; - } - else - { - _boundaryType = BoundaryType.ContentLength; - _contentLength = 0; - } - } - - if (_boundaryType != BoundaryType.Chunked) - { - if (_boundaryType != BoundaryType.ContentLength && closing) - { - _contentLength = CanSendResponseBody(_httpContext.Response.StatusCode) ? -1 : 0; - } - - if (_boundaryType == BoundaryType.ContentLength) - { - _webHeaders.Set("Content-Length", _contentLength.ToString("D", CultureInfo.InvariantCulture)); - } - } - - /* 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 = (_statusCode == (int)HttpStatusCode.BadRequest || _statusCode == (int)HttpStatusCode.RequestTimeout - || _statusCode == (int)HttpStatusCode.LengthRequired || _statusCode == (int)HttpStatusCode.RequestEntityTooLarge - || _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError - || _statusCode == (int)HttpStatusCode.ServiceUnavailable); - - if (!conn_close) - { - conn_close = !_httpContext.Request.KeepAlive; - } - - // They sent both KeepAlive: true and Connection: close - if (!_keepAlive || conn_close) - { - _webHeaders.Set("Connection", "Close"); - conn_close = true; - } - - if (SendChunked) - { - _webHeaders.Set("Transfer-Encoding", "Chunked"); - } - - int reuses = _httpContext.Connection.Reuses; - if (reuses >= 100) - { - _forceCloseChunked = true; - if (!conn_close) - { - _webHeaders.Set("Connection", "Close"); - conn_close = true; - } - } - - if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10) - { - if (_keepAlive) - { - Headers["Keep-Alive"] = "true"; - } - - if (!conn_close) - { - _webHeaders.Set("Connection", "Keep-Alive"); - } - } - - ComputeCookies(); - } - - var encoding = Encoding.UTF8; - var writer = new StreamWriter(ms, encoding, 256); - writer.Write("HTTP/1.1 {0} ", _statusCode); // "1.1" matches Windows implementation, which ignores the response version - writer.Flush(); - byte[] statusDescriptionBytes = WebHeaderEncoding.GetBytes(StatusDescription); - ms.Write(statusDescriptionBytes, 0, statusDescriptionBytes.Length); - writer.Write("\r\n"); - - writer.Write(FormatHeaders(_webHeaders)); - writer.Flush(); - int preamble = encoding.GetPreamble().Length; - EnsureResponseStream(); - - /* Assumes that the ms was at position 0 */ - ms.Position = preamble; - SentHeaders = !isWebSocketHandshake; - } - - private static bool HeaderCanHaveEmptyValue(string name) => - !string.Equals(name, "Location", StringComparison.OrdinalIgnoreCase); - - private static string FormatHeaders(WebHeaderCollection headers) - { - var sb = new StringBuilder(); - - for (int i = 0; i < headers.Count; i++) - { - string key = headers.GetKey(i); - string[] values = headers.GetValues(i); - - int startingLength = sb.Length; - - sb.Append(key).Append(": "); - bool anyValues = false; - for (int j = 0; j < values.Length; j++) - { - string value = values[j]; - if (!string.IsNullOrWhiteSpace(value)) - { - if (anyValues) - { - sb.Append(", "); - } - sb.Append(value); - anyValues = true; - } - } - - if (anyValues || HeaderCanHaveEmptyValue(key)) - { - // Complete the header - sb.Append("\r\n"); - } - else - { - // Empty header; remove it. - sb.Length = startingLength; - } - } - - return sb.Append("\r\n").ToString(); - } - - private bool Disposed { get; set; } - internal bool SentHeaders { get; set; } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken); - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerResponse.cs b/SocketHttpListener/Net/HttpListenerResponse.cs deleted file mode 100644 index 66ef885643..0000000000 --- a/SocketHttpListener/Net/HttpListenerResponse.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - public sealed 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 => _webHeaders; - - public Encoding ContentEncoding { get; set; } - - public string ContentType - { - get => Headers["Content-Type"]; - set - { - CheckDisposed(); - if (string.IsNullOrEmpty(value)) - { - Headers.Remove("Content-Type"); - } - else - { - Headers.Set("Content-Type", value); - } - } - } - - private HttpListenerContext HttpListenerContext => _httpContext; - - private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request; - - internal EntitySendFormat EntitySendFormat - { - get => (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 => 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 => _contentLength; - set - { - CheckDisposed(); - CheckSentHeaders(); - if (value >= 0) - { - _contentLength = value; - _boundaryType = BoundaryType.ContentLength; - } - else - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - } - } - - public CookieCollection Cookies - { - get => _cookies ?? (_cookies = new CookieCollection()); - set => _cookies = value; - } - - public bool KeepAlive - { - get => _keepAlive; - set - { - CheckDisposed(); - _keepAlive = value; - } - } - - public Stream OutputStream - { - get - { - CheckDisposed(); - EnsureResponseStream(); - return _responseStream; - } - } - - public string RedirectLocation - { - get => 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(); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpRequestStream.Managed.cs b/SocketHttpListener/Net/HttpRequestStream.Managed.cs deleted file mode 100644 index 42fc4d97c2..0000000000 --- a/SocketHttpListener/Net/HttpRequestStream.Managed.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.IO; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpRequestStream : Stream - { - private byte[] _buffer; - private int _offset; - private int _length; - private long _remainingBody; - protected bool _closed; - private Stream _stream; - - internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length) - : this(stream, buffer, offset, length, -1) - { - } - - internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength) - { - _stream = stream; - _buffer = buffer; - _offset = offset; - _length = length; - _remainingBody = contentlength; - } - - // Returns 0 if we can keep reading from the base stream, - // > 0 if we read something from the buffer. - // -1 if we had a content length set and we finished reading that many bytes. - private int FillFromBuffer(byte[] buffer, int offset, int count) - { - if (_remainingBody == 0) - return -1; - - if (_length == 0) - return 0; - - int size = Math.Min(_length, count); - if (_remainingBody > 0) - size = (int)Math.Min(size, _remainingBody); - - if (_offset > _buffer.Length - size) - { - size = Math.Min(size, _buffer.Length - _offset); - } - if (size == 0) - return 0; - - Buffer.BlockCopy(_buffer, _offset, buffer, offset, size); - _offset += size; - _length -= size; - if (_remainingBody > 0) - _remainingBody -= size; - return size; - } - - protected virtual int ReadCore(byte[] buffer, int offset, int size) - { - // Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0 - int nread = FillFromBuffer(buffer, offset, size); - if (nread == -1) - { // No more bytes available (Content-Length) - return 0; - } - else if (nread > 0) - { - return nread; - } - - if (_remainingBody > 0) - { - size = (int)Math.Min(_remainingBody, (long)size); - } - - nread = _stream.Read(buffer, offset, size); - - if (_remainingBody > 0) - { - if (nread == 0) - { - throw new Exception("Bad request"); - } - - //Debug.Assert(nread <= _remainingBody); - _remainingBody -= nread; - } - - return nread; - } - - protected virtual IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - if (size == 0 || _closed) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - ares.Complete(); - return ares; - } - - int nread = FillFromBuffer(buffer, offset, size); - if (nread > 0 || nread == -1) - { - var ares = new HttpStreamAsyncResult(this); - ares._buffer = buffer; - ares._offset = offset; - ares._count = size; - ares._callback = cback; - ares._state = state; - ares._synchRead = Math.Max(0, nread); - ares.Complete(); - return ares; - } - - // Avoid reading past the end of the request to allow - // for HTTP pipelining - if (_remainingBody >= 0 && size > _remainingBody) - { - size = (int)Math.Min(_remainingBody, (long)size); - } - - return _stream.BeginRead(buffer, offset, size, cback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - var r = asyncResult as HttpStreamAsyncResult; - if (r != null) - { - if (!ReferenceEquals(this, r._parent)) - { - throw new ArgumentException("Invalid async result"); - } - if (r._endCalled) - { - throw new InvalidOperationException("invalid end call"); - } - r._endCalled = true; - - if (!asyncResult.IsCompleted) - { - asyncResult.AsyncWaitHandle.WaitOne(); - } - - return r._synchRead; - } - - if (_closed) - return 0; - - int nread = 0; - try - { - nread = _stream.EndRead(asyncResult); - } - catch (IOException e) when (e.InnerException is ArgumentException || e.InnerException is InvalidOperationException) - { - throw e.InnerException; - } - - if (_remainingBody > 0) - { - if (nread == 0) - { - throw new Exception("Bad request"); - } - - _remainingBody -= nread; - } - - return nread; - } - } -} diff --git a/SocketHttpListener/Net/HttpRequestStream.cs b/SocketHttpListener/Net/HttpRequestStream.cs deleted file mode 100644 index 1c554df20b..0000000000 --- a/SocketHttpListener/Net/HttpRequestStream.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpRequestStream : Stream - { - public override bool CanSeek => false; - public override bool CanWrite => false; - public override bool CanRead => true; - - public override int Read(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - if (size == 0 || _closed) - { - return 0; - } - - return ReadCore(buffer, offset, size); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return BeginReadCore(buffer, offset, size, callback, state); - } - - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return base.BeginWrite(buffer, offset, count, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - base.EndWrite(asyncResult); - } - - internal bool Closed => _closed; - - protected override void Dispose(bool disposing) - { - _closed = true; - base.Dispose(disposing); - } - } -} diff --git a/SocketHttpListener/Net/HttpResponseStream.Managed.cs b/SocketHttpListener/Net/HttpResponseStream.Managed.cs deleted file mode 100644 index 5d02a9c956..0000000000 --- a/SocketHttpListener/Net/HttpResponseStream.Managed.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpResponseStream : Stream - { - private HttpListenerResponse _response; - private bool _ignore_errors; - private bool _trailer_sent; - private Stream _stream; - private readonly IStreamHelper _streamHelper; - private readonly Socket _socket; - private readonly bool _supportsDirectSocketAccess; - private readonly IEnvironmentInfo _environment; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - - internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IStreamHelper streamHelper, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger) - { - _response = response; - _ignore_errors = ignore_errors; - _streamHelper = streamHelper; - _socket = socket; - _supportsDirectSocketAccess = supportsDirectSocketAccess; - _environment = environment; - _fileSystem = fileSystem; - _logger = logger; - _stream = stream; - } - - private void DisposeCore() - { - byte[] bytes = null; - MemoryStream ms = GetHeaders(true); - bool chunked = _response.SendChunked; - if (_stream.CanWrite) - { - try - { - if (ms != null) - { - long start = ms.Position; - if (chunked && !_trailer_sent) - { - bytes = GetChunkSizeBytes(0, true); - ms.Position = ms.Length; - ms.Write(bytes, 0, bytes.Length); - } - InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start)); - _trailer_sent = true; - } - else if (chunked && !_trailer_sent) - { - bytes = GetChunkSizeBytes(0, true); - InternalWrite(bytes, 0, bytes.Length); - _trailer_sent = true; - } - } - catch (HttpListenerException) - { - // Ignore error due to connection reset by peer - } - } - _response.Close(); - } - - internal async Task WriteWebSocketHandshakeHeadersAsync() - { - if (_closed) - throw new ObjectDisposedException(GetType().ToString()); - - if (_stream.CanWrite) - { - MemoryStream ms = GetHeaders(closing: false, isWebSocketHandshake: true); - bool chunked = _response.SendChunked; - - long start = ms.Position; - if (chunked) - { - byte[] bytes = GetChunkSizeBytes(0, true); - ms.Position = ms.Length; - ms.Write(bytes, 0, bytes.Length); - } - - await InternalWriteAsync(ms.GetBuffer(), (int)start, (int)(ms.Length - start)).ConfigureAwait(false); - await _stream.FlushAsync().ConfigureAwait(false); - } - } - - private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false) - { - //// SendHeaders works on shared headers - //lock (_response.headers_lock) - //{ - // if (_response.HeadersSent) - // return null; - // var ms = CreateNew(); - // _response.SendHeaders(closing, ms); - // return ms; - //} - - // SendHeaders works on shared headers - lock (_response._headersLock) - { - if (_response.SentHeaders) - { - return null; - } - - MemoryStream ms = new MemoryStream(); - _response.SendHeaders(closing, ms, isWebSocketHandshake); - return ms; - } - } - - private static byte[] s_crlf = new byte[] { 13, 10 }; - private static byte[] GetChunkSizeBytes(int size, bool final) - { - string str = string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""); - return Encoding.ASCII.GetBytes(str); - } - - internal void InternalWrite(byte[] buffer, int offset, int count) - { - if (_ignore_errors) - { - try - { - _stream.Write(buffer, offset, count); - } - catch { } - } - else - { - _stream.Write(buffer, offset, count); - } - } - - internal Task InternalWriteAsync(byte[] buffer, int offset, int count) => - _ignore_errors ? InternalWriteIgnoreErrorsAsync(buffer, offset, count) : _stream.WriteAsync(buffer, offset, count); - - private async Task InternalWriteIgnoreErrorsAsync(byte[] buffer, int offset, int count) - { - try { await _stream.WriteAsync(buffer, offset, count).ConfigureAwait(false); } - catch { } - } - - private void WriteCore(byte[] buffer, int offset, int size) - { - if (size == 0) - return; - - byte[] bytes = null; - MemoryStream ms = GetHeaders(false); - bool chunked = _response.SendChunked; - if (ms != null) - { - long start = ms.Position; // After the possible preamble for the encoding - ms.Position = ms.Length; - if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - ms.Write(bytes, 0, bytes.Length); - } - - int new_count = Math.Min(size, 16384 - (int)ms.Position + (int)start); - ms.Write(buffer, offset, new_count); - size -= new_count; - offset += new_count; - InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start)); - ms.SetLength(0); - ms.Capacity = 0; // 'dispose' the buffer in ms. - } - else if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - InternalWrite(bytes, 0, bytes.Length); - } - - if (size > 0) - InternalWrite(buffer, offset, size); - if (chunked) - InternalWrite(s_crlf, 0, 2); - } - - private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - if (_closed) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - ares.Complete(); - return ares; - } - - byte[] bytes = null; - MemoryStream ms = GetHeaders(false); - bool chunked = _response.SendChunked; - if (ms != null) - { - long start = ms.Position; - ms.Position = ms.Length; - if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - ms.Write(bytes, 0, bytes.Length); - } - ms.Write(buffer, offset, size); - buffer = ms.GetBuffer(); - offset = (int)start; - size = (int)(ms.Position - start); - } - else if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - InternalWrite(bytes, 0, bytes.Length); - } - - return _stream.BeginWrite(buffer, offset, size, cback, state); - } - - private void EndWriteCore(IAsyncResult asyncResult) - { - if (_closed) - return; - - if (_ignore_errors) - { - try - { - _stream.EndWrite(asyncResult); - if (_response.SendChunked) - _stream.Write(s_crlf, 0, 2); - } - catch { } - } - else - { - _stream.EndWrite(asyncResult); - if (_response.SendChunked) - _stream.Write(s_crlf, 0, 2); - } - } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken); - } - - const int StreamCopyToBufferSize = 81920; - private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.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; - } - - var targetStream = this; - - if (count > 0) - { - await _streamHelper.CopyToAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); - } - else - { - await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpResponseStream.cs b/SocketHttpListener/Net/HttpResponseStream.cs deleted file mode 100644 index 085c2ad0cb..0000000000 --- a/SocketHttpListener/Net/HttpResponseStream.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - internal sealed partial class HttpResponseStream : Stream - { - private bool _closed; - internal bool Closed => _closed; - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return base.BeginRead(buffer, offset, count, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return base.EndRead(asyncResult); - } - - public override void Write(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - if (_closed) - { - return; - } - - WriteCore(buffer, offset, size); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return BeginWriteCore(buffer, offset, size, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult == null) - { - throw new ArgumentNullException(nameof(asyncResult)); - } - - EndWriteCore(asyncResult); - } - - protected override void Dispose(bool disposing) - { - try - { - if (disposing) - { - if (_closed) - { - return; - } - _closed = true; - DisposeCore(); - } - } - finally - { - base.Dispose(disposing); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpStatusCode.cs b/SocketHttpListener/Net/HttpStatusCode.cs deleted file mode 100644 index d4bb61b8a7..0000000000 --- a/SocketHttpListener/Net/HttpStatusCode.cs +++ /dev/null @@ -1,321 +0,0 @@ -namespace SocketHttpListener.Net -{ - /// - /// Contains the values of the HTTP status codes. - /// - /// - /// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in - /// RFC 2616 for HTTP 1.1. - /// - public enum HttpStatusCode - { - /// - /// Equivalent to status code 100. - /// Indicates that the client should continue with its request. - /// - Continue = 100, - /// - /// Equivalent to status code 101. - /// Indicates that the server is switching the HTTP version or protocol on the connection. - /// - SwitchingProtocols = 101, - /// - /// Equivalent to status code 200. - /// Indicates that the client's request has succeeded. - /// - OK = 200, - /// - /// Equivalent to status code 201. - /// Indicates that the client's request has been fulfilled and resulted in a new resource being - /// created. - /// - Created = 201, - /// - /// Equivalent to status code 202. - /// Indicates that the client's request has been accepted for processing, but the processing - /// hasn't been completed. - /// - Accepted = 202, - /// - /// Equivalent to status code 203. - /// Indicates that the returned metainformation is from a local or a third-party copy instead of - /// the origin server. - /// - NonAuthoritativeInformation = 203, - /// - /// Equivalent to status code 204. - /// Indicates that the server has fulfilled the client's request but doesn't need to return - /// an entity-body. - /// - NoContent = 204, - /// - /// Equivalent to status code 205. - /// Indicates that the server has fulfilled the client's request, and the user agent should - /// reset the document view which caused the request to be sent. - /// - ResetContent = 205, - /// - /// Equivalent to status code 206. - /// Indicates that the server has fulfilled the partial GET request for the resource. - /// - PartialContent = 206, - /// - /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. - /// - /// - /// MultipleChoices is a synonym for Ambiguous. - /// - /// - MultipleChoices = 300, - /// - /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. - /// - /// - /// Ambiguous is a synonym for MultipleChoices. - /// - /// - Ambiguous = 300, - /// - /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. - /// - /// - /// MovedPermanently is a synonym for Moved. - /// - /// - MovedPermanently = 301, - /// - /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. - /// - /// - /// Moved is a synonym for MovedPermanently. - /// - /// - Moved = 301, - /// - /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// Found is a synonym for Redirect. - /// - /// - Found = 302, - /// - /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// Redirect is a synonym for Found. - /// - /// - Redirect = 302, - /// - /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. - /// - /// - /// SeeOther is a synonym for RedirectMethod. - /// - /// - SeeOther = 303, - /// - /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. - /// - /// - /// RedirectMethod is a synonym for SeeOther. - /// - /// - RedirectMethod = 303, - /// - /// Equivalent to status code 304. - /// Indicates that the client has performed a conditional GET request and access is allowed, - /// but the document hasn't been modified. - /// - NotModified = 304, - /// - /// Equivalent to status code 305. - /// Indicates that the requested resource must be accessed through the proxy given by - /// the Location field. - /// - UseProxy = 305, - /// - /// Equivalent to status code 306. - /// This status code was used in a previous version of the specification, is no longer used, - /// and is reserved for future use. - /// - Unused = 306, - /// - /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// TemporaryRedirect is a synonym for RedirectKeepVerb. - /// - /// - TemporaryRedirect = 307, - /// - /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// RedirectKeepVerb is a synonym for TemporaryRedirect. - /// - /// - RedirectKeepVerb = 307, - /// - /// Equivalent to status code 400. - /// Indicates that the client's request couldn't be understood by the server due to - /// malformed syntax. - /// - BadRequest = 400, - /// - /// Equivalent to status code 401. - /// Indicates that the client's request requires user authentication. - /// - Unauthorized = 401, - /// - /// Equivalent to status code 402. - /// This status code is reserved for future use. - /// - PaymentRequired = 402, - /// - /// Equivalent to status code 403. - /// Indicates that the server understood the client's request but is refusing to fulfill it. - /// - Forbidden = 403, - /// - /// Equivalent to status code 404. - /// Indicates that the server hasn't found anything matching the request URI. - /// - NotFound = 404, - /// - /// Equivalent to status code 405. - /// Indicates that the method specified in the request line isn't allowed for the resource - /// identified by the request URI. - /// - MethodNotAllowed = 405, - /// - /// Equivalent to status code 406. - /// Indicates that the server doesn't have the appropriate resource to respond to the Accept - /// headers in the client's request. - /// - NotAcceptable = 406, - /// - /// Equivalent to status code 407. - /// Indicates that the client must first authenticate itself with the proxy. - /// - ProxyAuthenticationRequired = 407, - /// - /// Equivalent to status code 408. - /// Indicates that the client didn't produce a request within the time that the server was - /// prepared to wait. - /// - RequestTimeout = 408, - /// - /// Equivalent to status code 409. - /// Indicates that the client's request couldn't be completed due to a conflict on the server. - /// - Conflict = 409, - /// - /// Equivalent to status code 410. - /// Indicates that the requested resource is no longer available at the server and - /// no forwarding address is known. - /// - Gone = 410, - /// - /// Equivalent to status code 411. - /// Indicates that the server refuses to accept the client's request without a defined - /// Content-Length. - /// - LengthRequired = 411, - /// - /// Equivalent to status code 412. - /// Indicates that the precondition given in one or more of the request headers evaluated to - /// false when it was tested on the server. - /// - PreconditionFailed = 412, - /// - /// Equivalent to status code 413. - /// Indicates that the entity of the client's request is larger than the server is willing or - /// able to process. - /// - RequestEntityTooLarge = 413, - /// - /// Equivalent to status code 414. - /// Indicates that the request URI is longer than the server is willing to interpret. - /// - RequestUriTooLong = 414, - /// - /// Equivalent to status code 415. - /// Indicates that the entity of the client's request is in a format not supported by - /// the requested resource for the requested method. - /// - UnsupportedMediaType = 415, - /// - /// Equivalent to status code 416. - /// Indicates that none of the range specifier values in a Range request header overlap - /// the current extent of the selected resource. - /// - RequestedRangeNotSatisfiable = 416, - /// - /// Equivalent to status code 417. - /// Indicates that the expectation given in an Expect request header couldn't be met by - /// the server. - /// - ExpectationFailed = 417, - /// - /// Equivalent to status code 500. - /// Indicates that the server encountered an unexpected condition which prevented it from - /// fulfilling the client's request. - /// - InternalServerError = 500, - /// - /// Equivalent to status code 501. - /// Indicates that the server doesn't support the functionality required to fulfill the client's - /// request. - /// - NotImplemented = 501, - /// - /// Equivalent to status code 502. - /// Indicates that a gateway or proxy server received an invalid response from the upstream - /// server. - /// - BadGateway = 502, - /// - /// Equivalent to status code 503. - /// Indicates that the server is currently unable to handle the client's request due to - /// a temporary overloading or maintenance of the server. - /// - ServiceUnavailable = 503, - /// - /// Equivalent to status code 504. - /// Indicates that a gateway or proxy server didn't receive a timely response from the upstream - /// server or some other auxiliary server. - /// - GatewayTimeout = 504, - /// - /// Equivalent to status code 505. - /// Indicates that the server doesn't support the HTTP version used in the client's request. - /// - HttpVersionNotSupported = 505, - } -} diff --git a/SocketHttpListener/Net/HttpStatusDescription.cs b/SocketHttpListener/Net/HttpStatusDescription.cs deleted file mode 100644 index a4e42560b9..0000000000 --- a/SocketHttpListener/Net/HttpStatusDescription.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static class HttpStatusDescription - { - internal static string Get(HttpStatusCode code) - { - return Get((int)code); - } - - internal static string Get(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 426: return "Upgrade Required"; // RFC 2817 - - 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 null; - } - } -} diff --git a/SocketHttpListener/Net/HttpStreamAsyncResult.cs b/SocketHttpListener/Net/HttpStreamAsyncResult.cs deleted file mode 100644 index 46944c624a..0000000000 --- a/SocketHttpListener/Net/HttpStreamAsyncResult.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - internal class HttpStreamAsyncResult : IAsyncResult - { - private object _locker = new object(); - private ManualResetEvent _handle; - private bool _completed; - - internal readonly object _parent; - internal byte[] _buffer; - internal int _offset; - internal int _count; - internal AsyncCallback _callback; - internal object _state; - internal int _synchRead; - internal Exception _error; - internal bool _endCalled; - - internal HttpStreamAsyncResult(object parent) - { - _parent = parent; - } - - public void Complete(Exception e) - { - _error = e; - Complete(); - } - - public void Complete() - { - lock (_locker) - { - if (_completed) - return; - - _completed = true; - if (_handle != null) - _handle.Set(); - - if (_callback != null) - Task.Run(() => _callback(this)); - } - } - - public object AsyncState => _state; - - public WaitHandle AsyncWaitHandle - { - get - { - lock (_locker) - { - if (_handle == null) - _handle = new ManualResetEvent(_completed); - } - - return _handle; - } - } - - public bool CompletedSynchronously => false; - - public bool IsCompleted - { - get - { - lock (_locker) - { - return _completed; - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpVersion.cs b/SocketHttpListener/Net/HttpVersion.cs deleted file mode 100644 index c0839b46d5..0000000000 --- a/SocketHttpListener/Net/HttpVersion.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace SocketHttpListener.Net -{ - // - // - public class HttpVersion - { - - public static readonly Version Version10 = new Version(1, 0); - public static readonly Version Version11 = new Version(1, 1); - - // pretty useless.. - public HttpVersion() { } - } -} diff --git a/SocketHttpListener/Net/ListenerPrefix.cs b/SocketHttpListener/Net/ListenerPrefix.cs deleted file mode 100644 index edfcb89043..0000000000 --- a/SocketHttpListener/Net/ListenerPrefix.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Net; - -namespace SocketHttpListener.Net -{ - internal sealed class ListenerPrefix - { - private string _original; - private string _host; - private ushort _port; - private string _path; - private bool _secure; - private IPAddress[] _addresses; - internal HttpListener _listener; - - public ListenerPrefix(string prefix) - { - _original = prefix; - Parse(prefix); - } - - public override string ToString() - { - return _original; - } - - public IPAddress[] Addresses - { - get => _addresses; - set => _addresses = value; - } - public bool Secure => _secure; - - public string Host => _host; - - public int Port => _port; - - public string Path => _path; - - // Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection. - public override bool Equals(object o) - { - var other = o as ListenerPrefix; - if (other == null) - return false; - - return (_original == other._original); - } - - public override int GetHashCode() - { - return _original.GetHashCode(); - } - - private void Parse(string uri) - { - ushort default_port = 80; - if (uri.StartsWith("https://")) - { - default_port = 443; - _secure = true; - } - - int length = uri.Length; - int start_host = uri.IndexOf(':') + 3; - if (start_host >= length) - throw new ArgumentException("net_listener_host"); - - int colon = uri.IndexOf(':', start_host, length - start_host); - int root; - if (colon > 0) - { - _host = uri.Substring(start_host, colon - start_host); - root = uri.IndexOf('/', colon, length - colon); - _port = (ushort)int.Parse(uri.Substring(colon + 1, root - colon - 1)); - _path = uri.Substring(root); - } - else - { - root = uri.IndexOf('/', start_host, length - start_host); - _host = uri.Substring(start_host, root - start_host); - _port = default_port; - _path = uri.Substring(root); - } - if (_path.Length != 1) - _path = _path.Substring(0, _path.Length - 1); - } - } -} diff --git a/SocketHttpListener/Net/UriScheme.cs b/SocketHttpListener/Net/UriScheme.cs deleted file mode 100644 index 33d1f09db6..0000000000 --- a/SocketHttpListener/Net/UriScheme.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static class UriScheme - { - public const string File = "file"; - public const string Ftp = "ftp"; - public const string Gopher = "gopher"; - public const string Http = "http"; - public const string Https = "https"; - public const string News = "news"; - public const string NetPipe = "net.pipe"; - public const string NetTcp = "net.tcp"; - public const string Nntp = "nntp"; - public const string Mailto = "mailto"; - public const string Ws = "ws"; - public const string Wss = "wss"; - - public const string SchemeDelimiter = "://"; - } -} diff --git a/SocketHttpListener/Net/WebHeaderCollection.cs b/SocketHttpListener/Net/WebHeaderCollection.cs deleted file mode 100644 index 34fca808be..0000000000 --- a/SocketHttpListener/Net/WebHeaderCollection.cs +++ /dev/null @@ -1,360 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net -{ - [ComVisible(true)] - public class WebHeaderCollection : QueryParamCollection - { - [Flags] - internal enum HeaderInfo - { - Request = 1, - Response = 1 << 1, - MultiValue = 1 << 10 - } - - static readonly bool[] allowed_chars = { - false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, true, false, true, true, true, true, false, false, false, true, - true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false, - false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - false, true, false - }; - - static readonly Dictionary headers; - - static WebHeaderCollection() - { - headers = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "Allow", HeaderInfo.MultiValue }, - { "Accept", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Accept-Charset", HeaderInfo.MultiValue }, - { "Accept-Encoding", HeaderInfo.MultiValue }, - { "Accept-Language", HeaderInfo.MultiValue }, - { "Accept-Ranges", HeaderInfo.MultiValue }, - { "Age", HeaderInfo.Response }, - { "Authorization", HeaderInfo.MultiValue }, - { "Cache-Control", HeaderInfo.MultiValue }, - { "Cookie", HeaderInfo.MultiValue }, - { "Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Content-Encoding", HeaderInfo.MultiValue }, - { "Content-Length", HeaderInfo.Request | HeaderInfo.Response }, - { "Content-Type", HeaderInfo.Request }, - { "Content-Language", HeaderInfo.MultiValue }, - { "Date", HeaderInfo.Request }, - { "Expect", HeaderInfo.Request | HeaderInfo.MultiValue}, - { "Host", HeaderInfo.Request }, - { "If-Match", HeaderInfo.MultiValue }, - { "If-Modified-Since", HeaderInfo.Request }, - { "If-None-Match", HeaderInfo.MultiValue }, - { "Keep-Alive", HeaderInfo.Response }, - { "Pragma", HeaderInfo.MultiValue }, - { "Proxy-Authenticate", HeaderInfo.MultiValue }, - { "Proxy-Authorization", HeaderInfo.MultiValue }, - { "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Range", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Referer", HeaderInfo.Request }, - { "Set-Cookie", HeaderInfo.MultiValue }, - { "Set-Cookie2", HeaderInfo.MultiValue }, - { "Server", HeaderInfo.Response }, - { "TE", HeaderInfo.MultiValue }, - { "Trailer", HeaderInfo.MultiValue }, - { "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue }, - { "Translate", HeaderInfo.Request | HeaderInfo.Response }, - { "Upgrade", HeaderInfo.MultiValue }, - { "User-Agent", HeaderInfo.Request }, - { "Vary", HeaderInfo.MultiValue }, - { "Via", HeaderInfo.MultiValue }, - { "Warning", HeaderInfo.MultiValue }, - { "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketAccept", HeaderInfo.Response }, - { "SecWebSocketExtensions", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketKey", HeaderInfo.Request }, - { "Sec-WebSocket-Protocol", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketVersion", HeaderInfo.Response | HeaderInfo. MultiValue } - }; - } - - // Methods - - public void Add(string header) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - int pos = header.IndexOf(':'); - if (pos == -1) - throw new ArgumentException("no colon found", nameof(header)); - - this.Add(header.Substring(0, pos), header.Substring(pos + 1)); - } - - public override void Add(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - this.AddWithoutValidate(name, value); - } - - protected void AddWithoutValidate(string headerName, string headerValue) - { - if (!IsHeaderName(headerName)) - throw new ArgumentException("invalid header name: " + headerName, nameof(headerName)); - if (headerValue == null) - headerValue = string.Empty; - else - headerValue = headerValue.Trim(); - if (!IsHeaderValue(headerValue)) - throw new ArgumentException("invalid header value: " + headerValue, nameof(headerValue)); - - AddValue(headerName, headerValue); - } - - internal void AddValue(string headerName, string headerValue) - { - base.Add(headerName, headerValue); - } - - internal List GetValues_internal(string header, bool split) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - - var values = base.GetValues(header); - if (values == null || values.Count == 0) - return null; - - if (split && IsMultiValue(header)) - { - List separated = null; - foreach (var value in values) - { - if (value.IndexOf(',') < 0) - { - if (separated != null) - separated.Add(value); - - continue; - } - - if (separated == null) - { - separated = new List(values.Count + 1); - foreach (var v in values) - { - if (v == value) - break; - - separated.Add(v); - } - } - - var slices = value.Split(','); - var slices_length = slices.Length; - if (value[value.Length - 1] == ',') - --slices_length; - - for (int i = 0; i < slices_length; ++i) - { - separated.Add(slices[i].Trim()); - } - } - - if (separated != null) - return separated; - } - - return values; - } - - public override List GetValues(string header) - { - return GetValues_internal(header, true); - } - - public override string[] GetValues(int index) - { - string[] values = base.GetValues(index); - - if (values == null || values.Length == 0) - { - return null; - } - - return values; - } - - public static bool IsRestricted(string headerName) - { - return IsRestricted(headerName, false); - } - - public static bool IsRestricted(string headerName, bool response) - { - if (headerName == null) - throw new ArgumentNullException(nameof(headerName)); - - if (headerName.Length == 0) - throw new ArgumentException("empty string", nameof(headerName)); - - if (!IsHeaderName(headerName)) - throw new ArgumentException("Invalid character in header"); - - if (!headers.TryGetValue(headerName, out HeaderInfo info)) - return false; - - var flag = response ? HeaderInfo.Response : HeaderInfo.Request; - return (info & flag) != 0; - } - - public override void Set(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (!IsHeaderName(name)) - throw new ArgumentException("invalid header name"); - if (value == null) - value = string.Empty; - else - value = value.Trim(); - if (!IsHeaderValue(value)) - throw new ArgumentException("invalid header value"); - - base.Set(name, value); - } - - internal string ToStringMultiValue() - { - var sb = new StringBuilder(); - - int count = base.Count; - for (int i = 0; i < count; i++) - { - string key = GetKey(i); - if (IsMultiValue(key)) - { - foreach (string v in GetValues(i)) - { - sb.Append(key) - .Append(": ") - .Append(v) - .Append("\r\n"); - } - } - else - { - sb.Append(key) - .Append(": ") - .Append(Get(i)) - .Append("\r\n"); - } - } - return sb.Append("\r\n").ToString(); - } - - public override string ToString() - { - var sb = new StringBuilder(); - - int count = base.Count; - for (int i = 0; i < count; i++) - sb.Append(GetKey(i)) - .Append(": ") - .Append(Get(i)) - .Append("\r\n"); - - return sb.Append("\r\n").ToString(); - } - - - // Internal Methods - - // With this we don't check for invalid characters in header. See bug #55994. - internal void SetInternal(string header) - { - int pos = header.IndexOf(':'); - if (pos == -1) - throw new ArgumentException("no colon found", nameof(header)); - - SetInternal(header.Substring(0, pos), header.Substring(pos + 1)); - } - - internal void SetInternal(string name, string value) - { - if (value == null) - value = string.Empty; - else - value = value.Trim(); - if (!IsHeaderValue(value)) - throw new ArgumentException("invalid header value"); - - if (IsMultiValue(name)) - { - base.Add(name, value); - } - else - { - base.Remove(name); - base.Set(name, value); - } - } - - internal static bool IsMultiValue(string headerName) - { - if (headerName == null) - return false; - - return headers.TryGetValue(headerName, out HeaderInfo info) && (info & HeaderInfo.MultiValue) != 0; - } - - internal static bool IsHeaderValue(string value) - { - // TEXT any 8 bit value except CTL's (0-31 and 127) - // but including \r\n space and \t - // after a newline at least one space or \t must follow - // certain header fields allow comments () - - int len = value.Length; - for (int i = 0; i < len; i++) - { - char c = value[i]; - if (c == 127) - return false; - if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t')) - return false; - if (c == '\n' && ++i < len) - { - c = value[i]; - if (c != ' ' && c != '\t') - return false; - } - } - - return true; - } - - internal static bool IsHeaderName(string name) - { - if (name == null || name.Length == 0) - return false; - - int len = name.Length; - for (int i = 0; i < len; i++) - { - char c = name[i]; - if (c > 126 || !allowed_chars[c]) - return false; - } - - return true; - } - } -} diff --git a/SocketHttpListener/Net/WebHeaderEncoding.cs b/SocketHttpListener/Net/WebHeaderEncoding.cs deleted file mode 100644 index 96e0cc85d8..0000000000 --- a/SocketHttpListener/Net/WebHeaderEncoding.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Text; - -namespace SocketHttpListener.Net -{ - // we use this static class as a helper class to encode/decode HTTP headers. - // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF - // and a byte in the range 0x00-0xFF (which is the range that can hit the network). - // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow. - // It doesn't work for string -> byte[] because of best-fit-mapping problems. - internal static class WebHeaderEncoding - { - // We don't want '?' replacement characters, just fail. - private static readonly Encoding s_utf8Decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - - internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount) - { - fixed (byte* pBytes = bytes) - return GetString(pBytes + byteIndex, byteCount); - } - - internal static unsafe string GetString(byte* pBytes, int byteCount) - { - if (byteCount < 1) - return ""; - - string s = new string('\0', byteCount); - - fixed (char* pStr = s) - { - char* pString = pStr; - while (byteCount >= 8) - { - pString[0] = (char)pBytes[0]; - pString[1] = (char)pBytes[1]; - pString[2] = (char)pBytes[2]; - pString[3] = (char)pBytes[3]; - pString[4] = (char)pBytes[4]; - pString[5] = (char)pBytes[5]; - pString[6] = (char)pBytes[6]; - pString[7] = (char)pBytes[7]; - pString += 8; - pBytes += 8; - byteCount -= 8; - } - for (int i = 0; i < byteCount; i++) - { - pString[i] = (char)pBytes[i]; - } - } - - return s; - } - - internal static int GetByteCount(string myString) - { - return myString.Length; - } - internal static unsafe void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) - { - if (myString.Length == 0) - { - return; - } - fixed (byte* bufferPointer = bytes) - { - byte* newBufferPointer = bufferPointer + byteIndex; - int finalIndex = charIndex + charCount; - while (charIndex < finalIndex) - { - *newBufferPointer++ = (byte)myString[charIndex++]; - } - } - } - internal static byte[] GetBytes(string myString) - { - byte[] bytes = new byte[myString.Length]; - if (myString.Length != 0) - { - GetBytes(myString, 0, myString.Length, bytes, 0); - } - return bytes; - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs deleted file mode 100644 index 5ed49ec47d..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Principal; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net.WebSockets -{ - public class HttpListenerWebSocketContext : WebSocketContext - { - private readonly Uri _requestUri; - private readonly QueryParamCollection _headers; - private readonly CookieCollection _cookieCollection; - private readonly IPrincipal _user; - private readonly bool _isAuthenticated; - private readonly bool _isLocal; - private readonly bool _isSecureConnection; - - private readonly string _origin; - private readonly IEnumerable _secWebSocketProtocols; - private readonly string _secWebSocketVersion; - private readonly string _secWebSocketKey; - - private readonly WebSocket _webSocket; - - internal HttpListenerWebSocketContext( - Uri requestUri, - QueryParamCollection headers, - CookieCollection cookieCollection, - IPrincipal user, - bool isAuthenticated, - bool isLocal, - bool isSecureConnection, - string origin, - IEnumerable secWebSocketProtocols, - string secWebSocketVersion, - string secWebSocketKey, - WebSocket webSocket) - { - _cookieCollection = new CookieCollection(); - _cookieCollection.Add(cookieCollection); - - //_headers = new NameValueCollection(headers); - _headers = headers; - _user = CopyPrincipal(user); - - _requestUri = requestUri; - _isAuthenticated = isAuthenticated; - _isLocal = isLocal; - _isSecureConnection = isSecureConnection; - _origin = origin; - _secWebSocketProtocols = secWebSocketProtocols; - _secWebSocketVersion = secWebSocketVersion; - _secWebSocketKey = secWebSocketKey; - _webSocket = webSocket; - } - - public override Uri RequestUri => _requestUri; - - public override QueryParamCollection Headers => _headers; - - public override string Origin => _origin; - - public override IEnumerable SecWebSocketProtocols => _secWebSocketProtocols; - - public override string SecWebSocketVersion => _secWebSocketVersion; - - public override string SecWebSocketKey => _secWebSocketKey; - - public override CookieCollection CookieCollection => _cookieCollection; - - public override IPrincipal User => _user; - - public override bool IsAuthenticated => _isAuthenticated; - - public override bool IsLocal => _isLocal; - - public override bool IsSecureConnection => _isSecureConnection; - - public override WebSocket WebSocket => _webSocket; - - private static IPrincipal CopyPrincipal(IPrincipal user) - { - if (user != null) - { - throw new NotImplementedException(); - } - - return null; - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs deleted file mode 100644 index 1cfd2dc902..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class HttpWebSocket - { - private const string SupportedVersion = "13"; - - internal static async Task AcceptWebSocketAsyncCore(HttpListenerContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment? internalBuffer = null) - { - ValidateOptions(subProtocol, receiveBufferSize, MinSendBufferSize, keepAliveInterval); - - // get property will create a new response if one doesn't exist. - HttpListenerResponse response = context.Response; - HttpListenerRequest request = context.Request; - ValidateWebSocketHeaders(context); - - string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - - // Optional for non-browser client - string origin = request.Headers[HttpKnownHeaderNames.Origin]; - - string[] secWebSocketProtocols = null; - bool shouldSendSecWebSocketProtocolHeader = - ProcessWebSocketProtocolHeader( - request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], - subProtocol, - out var outgoingSecWebSocketProtocolString); - - if (shouldSendSecWebSocketProtocolHeader) - { - secWebSocketProtocols = new string[] { outgoingSecWebSocketProtocolString }; - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, outgoingSecWebSocketProtocolString); - } - - // negotiate the websocket key return value - string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = HttpWebSocket.GetSecWebSocketAcceptString(secWebSocketKey); - - response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketUpgradeToken); - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); - - response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101 - response.StatusDescription = HttpStatusDescription.Get(HttpStatusCode.SwitchingProtocols); - - var responseStream = response.OutputStream as HttpResponseStream; - - // Send websocket handshake headers - await responseStream.WriteWebSocketHandshakeHeadersAsync().ConfigureAwait(false); - - //WebSocket webSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval); - var webSocket = new WebSocket(subProtocol); - - var webSocketContext = new HttpListenerWebSocketContext( - request.Url, - request.Headers, - request.Cookies, - context.User, - request.IsAuthenticated, - request.IsLocal, - request.IsSecureConnection, - origin, - secWebSocketProtocols != null ? secWebSocketProtocols : Array.Empty(), - secWebSocketVersion, - secWebSocketKey, - webSocket); - - webSocket.SetContext(webSocketContext, context.Connection.Close, context.Connection.Stream); - - return webSocketContext; - } - - private const bool WebSocketsSupported = true; - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs deleted file mode 100644 index b346cc98ec..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using System.Text; -using System.Threading; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class HttpWebSocket - { - internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - internal const string WebSocketUpgradeToken = "websocket"; - internal const int DefaultReceiveBufferSize = 16 * 1024; - internal const int DefaultClientSendBufferSize = 16 * 1024; - - [SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 used only for hashing purposes, not for crypto.")] - internal static string GetSecWebSocketAcceptString(string secWebSocketKey) - { - string retVal; - - // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. - using (var sha1 = SHA1.Create()) - { - string acceptString = string.Concat(secWebSocketKey, HttpWebSocket.SecWebSocketKeyGuid); - byte[] toHash = Encoding.UTF8.GetBytes(acceptString); - retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); - } - - return retVal; - } - - // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server. - internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, - string subProtocol, - out string acceptProtocol) - { - acceptProtocol = string.Empty; - if (string.IsNullOrEmpty(clientSecWebSocketProtocol)) - { - // client hasn't specified any Sec-WebSocket-Protocol header - if (subProtocol != null) - { - // If the server specified _anything_ this isn't valid. - throw new WebSocketException("UnsupportedProtocol"); - } - // Treat empty and null from the server as the same thing here, server should not send headers. - return false; - } - - // here, we know the client specified something and it's non-empty. - - if (subProtocol == null) - { - // client specified some protocols, server specified 'null'. So server should send headers. - return true; - } - - // here, we know that the client has specified something, it's not empty - // and the server has specified exactly one protocol - - string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' }, - StringSplitOptions.RemoveEmptyEntries); - acceptProtocol = subProtocol; - - // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that - // this exists in the list the client specified. - for (int i = 0; i < requestProtocols.Length; i++) - { - string currentRequestProtocol = requestProtocols[i].Trim(); - if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - throw new WebSocketException("net_WebSockets_AcceptUnsupportedProtocol"); - } - - internal static void ValidateOptions(string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval) - { - if (subProtocol != null) - { - WebSocketValidate.ValidateSubprotocol(subProtocol); - } - - if (receiveBufferSize < MinReceiveBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too small."); - } - - if (sendBufferSize < MinSendBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too small."); - } - - if (receiveBufferSize > MaxBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too large."); - } - - if (sendBufferSize > MaxBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too large."); - } - - if (keepAliveInterval < Timeout.InfiniteTimeSpan) // -1 millisecond - { - throw new ArgumentOutOfRangeException(nameof(keepAliveInterval), "The keepAliveInterval was too small."); - } - } - - internal const int MinSendBufferSize = 16; - internal const int MinReceiveBufferSize = 256; - internal const int MaxBufferSize = 64 * 1024; - - private static void ValidateWebSocketHeaders(HttpListenerContext context) - { - if (!WebSocketsSupported) - { - throw new PlatformNotSupportedException("net_WebSockets_UnsupportedPlatform"); - } - - if (!context.Request.IsWebSocketRequest) - { - throw new WebSocketException("net_WebSockets_AcceptNotAWebSocket"); - } - - string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (string.IsNullOrEmpty(secWebSocketVersion)) - { - throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound"); - } - - if (!string.Equals(secWebSocketVersion, SupportedVersion, StringComparison.OrdinalIgnoreCase)) - { - throw new WebSocketException("net_WebSockets_AcceptUnsupportedWebSocketVersion"); - } - - string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - bool isSecWebSocketKeyInvalid = string.IsNullOrWhiteSpace(secWebSocketKey); - if (!isSecWebSocketKeyInvalid) - { - try - { - // key must be 16 bytes then base64-encoded - isSecWebSocketKeyInvalid = Convert.FromBase64String(secWebSocketKey).Length != 16; - } - catch - { - isSecWebSocketKeyInvalid = true; - } - } - if (isSecWebSocketKeyInvalid) - { - throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound"); - } - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs b/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs deleted file mode 100644 index 5ac89cf480..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace SocketHttpListener.Net.WebSockets -{ - public enum WebSocketCloseStatus - { - NormalClosure = 1000, - EndpointUnavailable = 1001, - ProtocolError = 1002, - InvalidMessageType = 1003, - Empty = 1005, - // AbnormalClosure = 1006, // 1006 is reserved and should never be used by user - InvalidPayloadData = 1007, - PolicyViolation = 1008, - MessageTooBig = 1009, - MandatoryExtension = 1010, - InternalServerError = 1011 - // TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user - - // 0 - 999 Status codes in the range 0-999 are not used. - // 1000 - 1999 Status codes in the range 1000-1999 are reserved for definition by this protocol. - // 2000 - 2999 Status codes in the range 2000-2999 are reserved for use by extensions. - // 3000 - 3999 Status codes in the range 3000-3999 MAY be used by libraries and frameworks. The - // interpretation of these codes is undefined by this protocol. End applications MUST - // NOT use status codes in this range. - // 4000 - 4999 Status codes in the range 4000-4999 MAY be used by application code. The interpretation - // of these codes is undefined by this protocol. - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs deleted file mode 100644 index 10ad864398..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Principal; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net.WebSockets -{ - public abstract class WebSocketContext - { - public abstract Uri RequestUri { get; } - public abstract QueryParamCollection Headers { get; } - public abstract string Origin { get; } - public abstract IEnumerable SecWebSocketProtocols { get; } - public abstract string SecWebSocketVersion { get; } - public abstract string SecWebSocketKey { get; } - public abstract CookieCollection CookieCollection { get; } - public abstract IPrincipal User { get; } - public abstract bool IsAuthenticated { get; } - public abstract bool IsLocal { get; } - public abstract bool IsSecureConnection { get; } - public abstract WebSocket WebSocket { get; } - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs b/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs deleted file mode 100644 index 0469e3b6c5..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class WebSocketValidate - { - internal const int MaxControlFramePayloadLength = 123; - private const int CloseStatusCodeAbort = 1006; - private const int CloseStatusCodeFailedTLSHandshake = 1015; - private const int InvalidCloseStatusCodesFrom = 0; - private const int InvalidCloseStatusCodesTo = 999; - private const string Separators = "()<>@,;:\\\"/[]?={} "; - - internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates) - { - string validStatesText = string.Empty; - - if (validStates != null && validStates.Length > 0) - { - foreach (WebSocketState validState in validStates) - { - if (currentState == validState) - { - // Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior. - if (isDisposed) - { - throw new ObjectDisposedException(nameof(WebSocket)); - } - - return; - } - } - - validStatesText = string.Join(", ", validStates); - } - - throw new WebSocketException("net_WebSockets_InvalidState"); - } - - internal static void ValidateSubprotocol(string subProtocol) - { - if (string.IsNullOrWhiteSpace(subProtocol)) - { - throw new ArgumentException("net_WebSockets_InvalidEmptySubProtocol"); - } - - string invalidChar = null; - int i = 0; - while (i < subProtocol.Length) - { - char ch = subProtocol[i]; - if (ch < 0x21 || ch > 0x7e) - { - invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch); - break; - } - - if (!char.IsLetterOrDigit(ch) && - Separators.IndexOf(ch) >= 0) - { - invalidChar = ch.ToString(); - break; - } - - i++; - } - - if (invalidChar != null) - { - throw new ArgumentException("net_WebSockets_InvalidCharInProtocolString"); - } - } - - internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription) - { - if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription)) - { - throw new ArgumentException("net_WebSockets_ReasonNotNull"); - } - - int closeStatusCode = (int)closeStatus; - - if ((closeStatusCode >= InvalidCloseStatusCodesFrom && - closeStatusCode <= InvalidCloseStatusCodesTo) || - closeStatusCode == CloseStatusCodeAbort || - closeStatusCode == CloseStatusCodeFailedTLSHandshake) - { - // CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort - throw new ArgumentException("net_WebSockets_InvalidCloseStatusCode"); - } - - int length = 0; - if (!string.IsNullOrEmpty(statusDescription)) - { - length = Encoding.UTF8.GetByteCount(statusDescription); - } - - if (length > MaxControlFramePayloadLength) - { - throw new ArgumentException("net_WebSockets_InvalidCloseStatusDescription"); - } - } - - internal static void ValidateArraySegment(ArraySegment arraySegment, string parameterName) - { - if (arraySegment.Array == null) - { - throw new ArgumentNullException(parameterName + "." + nameof(arraySegment.Array)); - } - if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length) - { - throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Offset)); - } - if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset)) - { - throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Count)); - } - } - - internal static void ValidateBuffer(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0 || count > (buffer.Length - offset)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - } - } -} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs index 0dcb6a64bc..afce871fd6 100644 --- a/SocketHttpListener/WebSocket.cs +++ b/SocketHttpListener/WebSocket.cs @@ -4,11 +4,10 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; +using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; using WebSocketState = System.Net.WebSockets.WebSocketState; namespace SocketHttpListener From 33b67a357fe73103f046b822d7cc66fc5318a9ef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 21 Feb 2019 12:32:47 +0100 Subject: [PATCH 02/59] Remove unused deps --- Jellyfin.Server/CoreAppHost.cs | 11 +------ .../SocketSharp/WebSocketSharpListener.cs | 32 +------------------ 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 84d78d3fb6..68fd2453e3 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -47,15 +47,6 @@ namespace Jellyfin.Server protected override void ShutdownInternal() => Program.Shutdown(); protected override IHttpListener CreateHttpListener() - => new WebSocketSharpListener( - Logger, - Certificate, - StreamHelper, - NetworkManager, - SocketFactory, - CryptographyProvider, - SupportsDualModeSockets, - FileSystemManager, - EnvironmentInfo); + => new WebSocketSharpListener(Logger); } } diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 64856f04af..c458fdb78f 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,18 +1,12 @@ using System; using System.Collections.Generic; using System.Net; - using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Net; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.SocketSharp @@ -22,38 +16,14 @@ using Microsoft.Extensions.Logging; private HttpListener _listener; private readonly ILogger _logger; - private readonly X509Certificate _certificate; - private readonly IStreamHelper _streamHelper; - private readonly INetworkManager _networkManager; - private readonly ISocketFactory _socketFactory; - private readonly ICryptoProvider _cryptoProvider; - private readonly IFileSystem _fileSystem; - private readonly bool _enableDualMode; - private readonly IEnvironmentInfo _environment; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationToken _disposeCancellationToken; public WebSocketSharpListener( - ILogger logger, - X509Certificate certificate, - IStreamHelper streamHelper, - INetworkManager networkManager, - ISocketFactory socketFactory, - ICryptoProvider cryptoProvider, - bool enableDualMode, - IFileSystem fileSystem, - IEnvironmentInfo environment) + ILogger logger) { _logger = logger; - _certificate = certificate; - _streamHelper = streamHelper; - _networkManager = networkManager; - _socketFactory = socketFactory; - _cryptoProvider = cryptoProvider; - _enableDualMode = enableDualMode; - _fileSystem = fileSystem; - _environment = environment; _disposeCancellationToken = _disposeCancellationTokenSource.Token; } From 852460b99155e015ed5f1d7ad2fab0281bfdfbec Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Feb 2019 23:34:32 +0100 Subject: [PATCH 03/59] kestrel init --- .../ApplicationHost.cs | 119 ++- .../Emby.Server.Implementations.csproj | 10 + .../HttpServer/HttpListenerHost.cs | 2 +- .../SocketSharp/HttpFile.cs | 18 + .../SocketSharp/HttpPostedFile.cs | 204 ++++++ .../SocketSharp/RequestMono.cs | 681 ++++++++++++++++++ .../SocketSharp/SharpWebSocket.cs | 149 ++++ .../SocketSharp/WebSocketSharpListener.cs | 261 +++++++ .../SocketSharp/WebSocketSharpRequest.cs | 539 ++++++++++++++ .../SocketSharp/WebSocketSharpResponse.cs | 206 ++++++ Emby.Server.Implementations/Startup.cs | 34 + Jellyfin.Server/Program.cs | 3 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 +- .../Services/QueryParamCollection.cs | 17 + 14 files changed, 2242 insertions(+), 5 deletions(-) create mode 100644 Emby.Server.Implementations/SocketSharp/HttpFile.cs create mode 100644 Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs create mode 100644 Emby.Server.Implementations/SocketSharp/RequestMono.cs create mode 100644 Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs create mode 100644 Emby.Server.Implementations/Startup.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 45a819f260..5e8611d531 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -42,6 +42,7 @@ using Emby.Server.Implementations.ScheduledTasks; using Emby.Server.Implementations.Security; using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; +using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; using Emby.Server.Implementations.Xml; @@ -105,10 +106,15 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using ServiceStack; +using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -123,7 +129,7 @@ namespace Emby.Server.Implementations /// /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - + public IWebHost Host { get; set; } public virtual bool CanLaunchWebBrowser { get @@ -612,6 +618,115 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection); FindParts(); + + Host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot("/Users/clausvium/RiderProjects/jellyfin/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin-web/src") + .UseStartup() +// .ConfigureServices(async services => +// { +// services.AddSingleton(startUp); +// RegisterResources(services); +// FindParts(); +// try +// { +// ImageProcessor.ImageEncoder = +// new NullImageEncoder(); //SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); +// } +// catch (Exception ex) +// { +// Logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); +// ImageProcessor.ImageEncoder = new NullImageEncoder(); +// } +// await RunStartupTasks().ConfigureAwait(false); +// }) + .UseUrls("http://localhost:8096") + .ConfigureServices(s => s.AddRouting()) + .Configure( app => + { + app.UseWebSockets(new WebSocketOptions { + KeepAliveInterval = TimeSpan.FromMilliseconds(1000000000), + ReceiveBufferSize = 0x10000 + }); + + app.UseRouter(r => + { + // TODO all the verbs, but really MVC... + r.MapGet("/{*localpath}", ExecuteHandler); + r.MapPut("/{*localpath}", ExecuteHandler); + r.MapPost("/{*localpath}", ExecuteHandler); + r.MapDelete("/{*localpath}", ExecuteHandler); + r.MapVerb("HEAD", "/{*localpath}", ExecuteHandler); + }); + }) + .Build(); + } + + public async Task ExecuteHandler(HttpRequest request, Microsoft.AspNetCore.Http.HttpResponse response, RouteData data) + { + var ctx = request.HttpContext; + if (ctx.WebSockets.IsWebSocketRequest) + { + try + { + var endpoint = ctx.Request.Path.ToString(); + var url = ctx.Request.Path.ToString(); + + var queryString = new QueryParamCollection(request.Query); + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = queryString, + Endpoint = endpoint + }; + + if (connectingArgs.AllowConnection) + { + Logger.LogDebug("Web socket connection allowed"); + + var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + //socket.ConnectAsServerAsync().ConfigureAwait(false); + +// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) +// { +// OnReceive = ProcessWebSocketMessageReceived, +// Url = e.Url, +// QueryString = e.QueryString ?? new QueryParamCollection() +// }; +// +// connection.Closed += Connection_Closed; +// +// lock (_webSocketConnections) +// { +// _webSocketConnections.Add(connection); +// } +// +// WebSocketConnected(new WebSocketConnectEventArgs +// { +// Url = url, +// QueryString = queryString, +// WebSocket = socket, +// Endpoint = endpoint +// }); + await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + } + else + { + Logger.LogWarning("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + } + } + catch (Exception ex) + { + ctx.Response.StatusCode = 500; + } + } + + var req = new WebSocketSharpRequest(request, response, request.Path, Logger); + await ((HttpListenerHost)HttpServer).RequestHandler(req,request.Path.ToString(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() @@ -1067,7 +1182,7 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports()); - StartServer(); + //StartServer(); LibraryManager.AddParts(GetExports(), GetExports(), diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index bbf165d627..acbc60c39e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,6 +22,16 @@ + + + + + + + + + + diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index fb42460f1b..1bd084259d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Overridable method that can be used to implement a custom hnandler /// - protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) + public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken) { var stopWatch = new Stopwatch(); stopWatch.Start(); diff --git a/Emby.Server.Implementations/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs new file mode 100644 index 0000000000..120ac50d9c --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/HttpFile.cs @@ -0,0 +1,18 @@ +using System.IO; +using MediaBrowser.Model.Services; + +namespace Emby.Server.Implementations.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/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs new file mode 100644 index 0000000000..f38ed848ee --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs @@ -0,0 +1,204 @@ +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/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs new file mode 100644 index 0000000000..a8142aef68 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -0,0 +1,681 @@ +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; +using Microsoft.Extensions.Primitives; + +namespace Emby.Server.Implementations.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 => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString(); + + public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); + + 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/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs new file mode 100644 index 0000000000..80ef451dc7 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -0,0 +1,149 @@ +using System; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations.Net; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.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/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs new file mode 100644 index 0000000000..ab7ddeca20 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -0,0 +1,261 @@ +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.AspNetCore.Http; +using Microsoft.Extensions.Logging; + + namespace Emby.Server.Implementations.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 = socket, + Endpoint = endpoint + }); + + await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + } + } + else + { + _logger.LogWarning("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + ctx.Response.Close(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "AcceptWebSocketAsync error"); + ctx.Response.StatusCode = 500; + ctx.Response.Close(); + } + } + + private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) + { + try + { + await socket.StartReceive().ConfigureAwait(false); + } + finally + { + TryClose(ctx, 200); + } + } + + private void TryClose(HttpListenerContext ctx, int statusCode) + { + try + { + ctx.Response.StatusCode = statusCode; + ctx.Response.Close(); + } + catch (ObjectDisposedException) + { + // TODO: Investigate and properly fix. + } + catch (Exception ex) + { + _logger.LogError(ex, "Error closing web socket response"); + } + } + + private IHttpRequest GetRequest(HttpRequest httpContext) + { + var urlSegments = httpContext.Path; + + var operationName = urlSegments; + + var req = new WebSocketSharpRequest(httpContext, httpContext.HttpContext.Response, operationName, _logger); + + return req; + } + + public void Start(IEnumerable urlPrefixes) + { + throw new NotImplementedException(); + } + + 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/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs new file mode 100644 index 0000000000..facc544461 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -0,0 +1,539 @@ +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.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +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 Emby.Server.Implementations.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + private readonly HttpRequest request; + private readonly IHttpResponse response; + + public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) + { + this.OperationName = operationName; + this.request = httpContext; + this.response = new WebSocketSharpResponse(logger, response, this); + + // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); + } + + public HttpRequest 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.Path.ToUriComponent(); + + public string AbsoluteUri => request.Path.ToUriComponent().TrimEnd('/'); + + public string UserHostAddress => ""; + + public string XForwardedFor + => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); + + public int? XForwardedPort + => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); + + public string XForwardedProtocol => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"].ToString(); + + public string XRealIp => StringValues.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"].ToString(); + + private string remoteIp; + + public string RemoteIp => + remoteIp ?? + (remoteIp = CheckBadChars(XForwardedFor) ?? + NormalizeIp(CheckBadChars(XRealIp) ?? + (string.IsNullOrEmpty(request.Host.Host) ? null : NormalizeIp(request.Host.Host)))); + + 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.IsHttps || XForwardedProtocol == "https"; + + public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); + + private Dictionary items; + public Dictionary Items => items ?? (items = new Dictionary()); + + private string responseContentType; + public string ResponseContentType + { + get => + responseContentType + ?? (responseContentType = GetResponseContentType(HttpRequest)); + set => this.responseContentType = value; + } + + public const string FormUrlEncoded = "application/x-www-form-urlencoded"; + public const string MultiPartFormData = "multipart/form-data"; + public static string GetResponseContentType(HttpRequest httpReq) + { + var specifiedContentType = GetQueryStringContentType(httpReq); + if (!string.IsNullOrEmpty(specifiedContentType)) + { + return specifiedContentType; + } + + const string serverDefaultContentType = "application/json"; + + var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); // TODO; + 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(HttpRequest 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(HttpRequest request, string contentType) + { + return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); + } + + private static string GetQueryStringContentType(HttpRequest httpReq) + { + string format = httpReq.Query["format"]; + if (format == null) + { + const int formatMaxLength = 4; + string pi = httpReq.Path.ToString(); + if (pi == null || pi.Length <= formatMaxLength) + { + return null; + } + + if (pi[0] == '/') + { + pi = pi.Substring(1); + } + + format = LeftPart(pi, '/'); + if (format.Length > formatMaxLength) + { + return null; + } + } + + format = LeftPart(format, '.'); + if (format.ToLower().Contains("json")) + { + return "application/json"; + } + else if (format.ToLower().Contains("xml")) + { + return "application/xml"; + } + + return null; + } + + public static string LeftPart(string strVal, char needle) + { + if (strVal == null) + { + return null; + } + + var pos = strVal.IndexOf(needle.ToString(), 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.ToString()); + 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.Path.ToString().IndexOf("?", StringComparison.Ordinal); + if (pos != -1) + { + var path = request.Path.ToString().Substring(0, pos); + this.pathInfo = GetPathInfo( + path, + mode, + mode ?? string.Empty); + } + else + { + this.pathInfo = request.Path.ToString(); + } + + 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 = cookie; + cookies[httpCookie.Key] = new Cookie(httpCookie.Key, httpCookie.Value, "", ""); + } + } + + return cookies; + } + } + + public string UserAgent => request.Headers[HeaderNames.UserAgent]; + + public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + + private QueryParamCollection queryString; + public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + + public bool IsLocal => true; // TODO + + private string httpMethod; + public string HttpMethod => + httpMethod + ?? (httpMethod = request.Method); + + public string Verb => HttpMethod; + + public string ContentType => request.ContentType; + + private Encoding contentEncoding; + public Encoding ContentEncoding + { + get => contentEncoding ?? Encoding.GetEncoding(request.Headers[HeaderNames.ContentEncoding].ToString()); + set => contentEncoding = value; + } + + public Uri UrlReferrer => request.GetTypedHeaders().Referer; + + 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.Body; + + public long ContentLength => request.ContentLength ?? 0; + + 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/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs new file mode 100644 index 0000000000..c68b959846 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -0,0 +1,206 @@ +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.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IRequest = MediaBrowser.Model.Services.IRequest; + +namespace Emby.Server.Implementations.SocketSharp +{ + public class WebSocketSharpResponse : IHttpResponse + { + private readonly ILogger _logger; + + private readonly HttpResponse _response; + + public WebSocketSharpResponse(ILogger logger, HttpResponse 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; set; } + + 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.Headers.Add(name, value); + } + + public string GetHeader(string name) + { + return _response.Headers[name]; + } + + public void Redirect(string url) + { + _response.Redirect(url); + } + + public Stream OutputStream => _response.Body; + + public void Close() + { + if (!this.IsClosed) + { + this.IsClosed = true; + + try + { + var response = this._response; + + var outputStream = response.Body; + + // This is needed with compression + outputStream.Flush(); + outputStream.Dispose(); + } + 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; set; } + + 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); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Startup.cs b/Emby.Server.Implementations/Startup.cs new file mode 100644 index 0000000000..164c7eeaa7 --- /dev/null +++ b/Emby.Server.Implementations/Startup.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using MediaBrowser.Api; +using MediaBrowser.Controller; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) => Configuration = configuration; + + // Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddRouting(); + } + + // Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + + } + } + +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 41ee73a565..bfc50468f7 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -20,6 +20,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -143,7 +144,7 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - + appHost.Host.Run(); // TODO: read input for a stop command try diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index f17fd7159d..2f1ea13d9b 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,6 +13,8 @@ + + diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 0e0ebf848b..09806ed9c2 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Linq; using System.Net; using MediaBrowser.Model.Dto; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -23,6 +24,14 @@ namespace MediaBrowser.Model.Services } } + public QueryParamCollection(Microsoft.AspNetCore.Http.IHeaderDictionary headers) + { + foreach (var pair in headers) + { + Add(pair.Key, pair.Value); + } + } + // TODO remove this shit public QueryParamCollection(WebHeaderCollection webHeaderCollection) { @@ -47,6 +56,14 @@ namespace MediaBrowser.Model.Services } } + public QueryParamCollection(IQueryCollection queryCollection) + { + foreach (var pair in queryCollection) + { + Add(pair.Key, pair.Value); + } + } + private static StringComparison GetStringComparison() { return StringComparison.OrdinalIgnoreCase; From c3fa299acc1612966ee0d778c3e86f71c21a252a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 07:22:49 +0100 Subject: [PATCH 04/59] Remove hardcoded path and fix url bug in Windows --- Emby.Server.Implementations/ApplicationHost.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5e8611d531..56089320ef 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -109,6 +109,7 @@ using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -621,8 +622,8 @@ namespace Emby.Server.Implementations Host = new WebHostBuilder() .UseKestrel() - .UseContentRoot("/Users/clausvium/RiderProjects/jellyfin/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin-web/src") - .UseStartup() + .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), @"jellyfin-web\src")) + //.UseStartup() // .ConfigureServices(async services => // { // services.AddSingleton(startUp); @@ -726,7 +727,7 @@ namespace Emby.Server.Implementations } var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req,request.Path.ToString(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); + await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() From f3e7bc0573e69df236ac8c565a354520dd094775 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 08:09:42 +0100 Subject: [PATCH 05/59] Replace some todos with http extensions and prepare some socket work --- .../ApplicationHost.cs | 122 ++++++++++-------- .../HttpServer/HttpListenerHost.cs | 7 + .../SocketSharp/WebSocketSharpListener.cs | 46 +++---- .../SocketSharp/WebSocketSharpRequest.cs | 13 +- 4 files changed, 102 insertions(+), 86 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 56089320ef..18e752e53b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -110,10 +110,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; @@ -642,7 +644,12 @@ namespace Emby.Server.Implementations // await RunStartupTasks().ConfigureAwait(false); // }) .UseUrls("http://localhost:8096") - .ConfigureServices(s => s.AddRouting()) + .ConfigureServices(services => + { + services.AddRouting(); + services.AddHttpContextAccessor(); + services.TryAddSingleton(); + }) .Configure( app => { app.UseWebSockets(new WebSocketOptions { @@ -668,62 +675,63 @@ namespace Emby.Server.Implementations var ctx = request.HttpContext; if (ctx.WebSockets.IsWebSocketRequest) { - try - { - var endpoint = ctx.Request.Path.ToString(); - var url = ctx.Request.Path.ToString(); - - var queryString = new QueryParamCollection(request.Query); - - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryString, - Endpoint = endpoint - }; - - if (connectingArgs.AllowConnection) - { - Logger.LogDebug("Web socket connection allowed"); - - var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - //socket.ConnectAsServerAsync().ConfigureAwait(false); - -// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) -// { -// OnReceive = ProcessWebSocketMessageReceived, -// Url = e.Url, -// QueryString = e.QueryString ?? new QueryParamCollection() -// }; -// -// connection.Closed += Connection_Closed; -// -// lock (_webSocketConnections) -// { -// _webSocketConnections.Add(connection); -// } -// -// WebSocketConnected(new WebSocketConnectEventArgs -// { -// Url = url, -// QueryString = queryString, -// WebSocket = socket, -// Endpoint = endpoint -// }); - await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - } - else - { - Logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; - } - } - catch (Exception ex) - { - ctx.Response.StatusCode = 500; - } + await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(ctx).ConfigureAwait(false); +// try +// { +// var endpoint = ctx.Request.Path.ToString(); +// var url = ctx.Request.Path.ToString(); + + // var queryString = new QueryParamCollection(request.Query); + + // var connectingArgs = new WebSocketConnectingEventArgs + // { + // Url = url, + // QueryString = queryString, + // Endpoint = endpoint + // }; + + // if (connectingArgs.AllowConnection) + // { + // Logger.LogDebug("Web socket connection allowed"); + + // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + // //socket.ConnectAsServerAsync().ConfigureAwait(false); + + //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) + //// { + //// OnReceive = ProcessWebSocketMessageReceived, + //// Url = e.Url, + //// QueryString = e.QueryString ?? new QueryParamCollection() + //// }; + //// + //// connection.Closed += Connection_Closed; + //// + //// lock (_webSocketConnections) + //// { + //// _webSocketConnections.Add(connection); + //// } + //// + //// WebSocketConnected(new WebSocketConnectEventArgs + //// { + //// Url = url, + //// QueryString = queryString, + //// WebSocket = socket, + //// Endpoint = endpoint + //// }); + // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + // } + // else + // { + // Logger.LogWarning("Web socket connection not allowed"); + // ctx.Response.StatusCode = 401; + // } + // } + // catch (Exception ex) + // { + // ctx.Response.StatusCode = 500; + // } } var req = new WebSocketSharpRequest(request, response, request.Path, Logger); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 1bd084259d..cfe0bbe974 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Services; +using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller; @@ -73,6 +74,10 @@ namespace Emby.Server.Implementations.HttpServer Instance = this; ResponseFilters = Array.Empty>(); + _websocketlistener = new WebSocketSharpListener(_logger) + { + WebSocketConnected = OnWebSocketConnected + }; } public string GlobalResponse { get; set; } @@ -796,6 +801,8 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); + public WebSocketSharpListener _websocketlistener; + protected virtual void Dispose(bool disposing) { if (_disposed) return; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index ab7ddeca20..824c9a822d 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -8,6 +8,7 @@ using Emby.Server.Implementations.Net; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.SocketSharp @@ -120,14 +121,14 @@ using Microsoft.Extensions.Logging; // return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); // } - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) + public async Task ProcessWebSocketRequest(HttpContext ctx) { try { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; + var endpoint = ctx.Connection.RemoteIpAddress.ToString(); + var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.QueryString); + var queryString = new QueryParamCollection(ctx.Request.Query); var connectingArgs = new WebSocketConnectingEventArgs { @@ -142,40 +143,40 @@ using Microsoft.Extensions.Logging; { _logger.LogDebug("Web socket connection allowed"); - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); + var webSocketContext = await ctx.WebSockets.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 = socket, - Endpoint = endpoint - }); - - await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); + //await socket.ConnectAsServerAsync().ConfigureAwait(false); + + //WebSocketConnected(new WebSocketConnectEventArgs + //{ + // Url = url, + // QueryString = queryString, + // WebSocket = socket, + // Endpoint = endpoint + //}); + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } } else { _logger.LogWarning("Web socket connection not allowed"); ctx.Response.StatusCode = 401; - ctx.Response.Close(); + //ctx.Response.Close(); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); ctx.Response.StatusCode = 500; - ctx.Response.Close(); + //ctx.Response.Close(); } } - private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) + private async Task ReceiveWebSocketAsync(HttpContext ctx, SharpWebSocket socket) { try { @@ -187,12 +188,11 @@ using Microsoft.Extensions.Logging; } } - private void TryClose(HttpListenerContext ctx, int statusCode) + private void TryClose(HttpContext ctx, int statusCode) { try { ctx.Response.StatusCode = statusCode; - ctx.Response.Close(); } catch (ObjectDisposedException) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index facc544461..9c5b2b0834 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -7,6 +7,7 @@ using System.Text; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; @@ -44,11 +45,11 @@ namespace Emby.Server.Implementations.SocketSharp public object Dto { get; set; } - public string RawUrl => request.Path.ToUriComponent(); + public string RawUrl => request.Path.ToString(); - public string AbsoluteUri => request.Path.ToUriComponent().TrimEnd('/'); + public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - public string UserHostAddress => ""; + public string UserHostAddress => request.HttpContext.Connection.RemoteIpAddress.ToString(); public string XForwardedFor => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); @@ -66,7 +67,7 @@ namespace Emby.Server.Implementations.SocketSharp remoteIp ?? (remoteIp = CheckBadChars(XForwardedFor) ?? NormalizeIp(CheckBadChars(XRealIp) ?? - (string.IsNullOrEmpty(request.Host.Host) ? null : NormalizeIp(request.Host.Host)))); + (string.IsNullOrEmpty(request.HttpContext.Connection.RemoteIpAddress.ToString()) ? null : NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString())))); private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; @@ -199,7 +200,7 @@ namespace Emby.Server.Implementations.SocketSharp const string serverDefaultContentType = "application/json"; - var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); // TODO; + var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); string defaultContentType = null; if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { @@ -448,7 +449,7 @@ namespace Emby.Server.Implementations.SocketSharp private QueryParamCollection queryString; public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); - public bool IsLocal => true; // TODO + public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); private string httpMethod; public string HttpMethod => From 194da8416bb93b492b86bab6dd199eb5dcc06e55 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 10:23:58 +0100 Subject: [PATCH 06/59] Use middlewares instead of Routing --- .../ApplicationHost.cs | 147 +++++++++--------- .../SocketSharp/SharpWebSocket.cs | 14 +- 2 files changed, 79 insertions(+), 82 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 18e752e53b..ca1dceedfa 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -657,85 +657,92 @@ namespace Emby.Server.Implementations ReceiveBufferSize = 0x10000 }); - app.UseRouter(r => - { - // TODO all the verbs, but really MVC... - r.MapGet("/{*localpath}", ExecuteHandler); - r.MapPut("/{*localpath}", ExecuteHandler); - r.MapPost("/{*localpath}", ExecuteHandler); - r.MapDelete("/{*localpath}", ExecuteHandler); - r.MapVerb("HEAD", "/{*localpath}", ExecuteHandler); - }); + app.Use(ExecuteWebsocketHandlerAsync); + app.Use(ExecuteHttpHandlerAsync); }) .Build(); } - public async Task ExecuteHandler(HttpRequest request, Microsoft.AspNetCore.Http.HttpResponse response, RouteData data) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { - var ctx = request.HttpContext; - if (ctx.WebSockets.IsWebSocketRequest) + if (!context.WebSockets.IsWebSocketRequest) { - await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(ctx).ConfigureAwait(false); -// try -// { -// var endpoint = ctx.Request.Path.ToString(); -// var url = ctx.Request.Path.ToString(); - - // var queryString = new QueryParamCollection(request.Query); - - // var connectingArgs = new WebSocketConnectingEventArgs - // { - // Url = url, - // QueryString = queryString, - // Endpoint = endpoint - // }; - - // if (connectingArgs.AllowConnection) - // { - // Logger.LogDebug("Web socket connection allowed"); - - // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - // //socket.ConnectAsServerAsync().ConfigureAwait(false); - - //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) - //// { - //// OnReceive = ProcessWebSocketMessageReceived, - //// Url = e.Url, - //// QueryString = e.QueryString ?? new QueryParamCollection() - //// }; - //// - //// connection.Closed += Connection_Closed; - //// - //// lock (_webSocketConnections) - //// { - //// _webSocketConnections.Add(connection); - //// } - //// - //// WebSocketConnected(new WebSocketConnectEventArgs - //// { - //// Url = url, - //// QueryString = queryString, - //// WebSocket = socket, - //// Endpoint = endpoint - //// }); - // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - // } - // else - // { - // Logger.LogWarning("Web socket connection not allowed"); - // ctx.Response.StatusCode = 401; - // } - // } - // catch (Exception ex) - // { - // ctx.Response.StatusCode = 500; - // } + await next(); + return; + } + + await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(context).ConfigureAwait(false); + // try + // { + // var endpoint = ctx.Request.Path.ToString(); + // var url = ctx.Request.Path.ToString(); + + // var queryString = new QueryParamCollection(request.Query); + + // var connectingArgs = new WebSocketConnectingEventArgs + // { + // Url = url, + // QueryString = queryString, + // Endpoint = endpoint + // }; + + // if (connectingArgs.AllowConnection) + // { + // Logger.LogDebug("Web socket connection allowed"); + + // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + // //socket.ConnectAsServerAsync().ConfigureAwait(false); + + //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) + //// { + //// OnReceive = ProcessWebSocketMessageReceived, + //// Url = e.Url, + //// QueryString = e.QueryString ?? new QueryParamCollection() + //// }; + //// + //// connection.Closed += Connection_Closed; + //// + //// lock (_webSocketConnections) + //// { + //// _webSocketConnections.Add(connection); + //// } + //// + //// WebSocketConnected(new WebSocketConnectEventArgs + //// { + //// Url = url, + //// QueryString = queryString, + //// WebSocket = socket, + //// Endpoint = endpoint + //// }); + // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + // } + // else + // { + // Logger.LogWarning("Web socket connection not allowed"); + // ctx.Response.StatusCode = 401; + // } + // } + // catch (Exception ex) + // { + // ctx.Response.StatusCode = 500; + // } + } + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + { + if (context.WebSockets.IsWebSocketRequest) + { + await next(); + return; } + var request = context.Request; + var response = context.Response; + var localPath = context.Request.Path.ToString().TrimStart('/'); + var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); + await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 80ef451dc7..854d33f3a0 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -28,18 +28,8 @@ namespace Emby.Server.Implementations.SocketSharp 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; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); socket.OnMessage += OnSocketMessage; socket.OnClose += OnSocketClose; From 38f52a139e8017ddaaef55e1eb4cc97f1d4d8c04 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 10:30:51 +0100 Subject: [PATCH 07/59] Add response compression middleware --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ca1dceedfa..da9550b851 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -646,7 +646,7 @@ namespace Emby.Server.Implementations .UseUrls("http://localhost:8096") .ConfigureServices(services => { - services.AddRouting(); + services.AddResponseCompression(); services.AddHttpContextAccessor(); services.TryAddSingleton(); }) @@ -657,6 +657,7 @@ namespace Emby.Server.Implementations ReceiveBufferSize = 0x10000 }); + app.UseResponseCompression(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); }) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index acbc60c39e..a4dac4a7f9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,6 +29,8 @@ + + From d6c6f3c10cff68b11df884be502c58a17da0d332 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 15:13:06 +0100 Subject: [PATCH 08/59] Still broken --- .../ApplicationHost.cs | 2 +- .../Net/WebSocketConnectEventArgs.cs | 1 + .../SocketSharp/SharpWebSocket.cs | 31 +++++------- .../SocketSharp/WebSocketSharpListener.cs | 48 +++++++++++++------ .../SocketSharp/WebSocketSharpListener.cs | 4 +- 5 files changed, 51 insertions(+), 35 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index da9550b851..65b6681c89 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -740,7 +740,7 @@ namespace Emby.Server.Implementations var request = context.Request; var response = context.Response; - var localPath = context.Request.Path.ToString().TrimStart('/'); + var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 3ab8e854a0..666f1f6011 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Net.WebSockets; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.Net diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 854d33f3a0..89004ba7fe 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -1,5 +1,6 @@ using System; using System.Net.WebSockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; @@ -20,25 +21,18 @@ namespace Emby.Server.Implementations.SocketSharp /// Gets or sets the web socket. /// /// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } + private 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) + public SharpWebSocket(WebSocket socket, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); - - socket.OnMessage += OnSocketMessage; - socket.OnClose += OnSocketClose; - socket.OnError += OnSocketError; } - public Task ConnectAsServerAsync() - => WebSocket.ConnectAsServer(); - public Task StartReceive() { return _taskCompletionSource.Task; @@ -58,7 +52,7 @@ namespace Emby.Server.Implementations.SocketSharp Closed?.Invoke(this, EventArgs.Empty); } - private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) + private void OnSocketMessage(SocketHttpListener.MessageEventArgs e) { if (OnReceiveBytes != null) { @@ -66,11 +60,15 @@ namespace Emby.Server.Implementations.SocketSharp } } + public Task ConnectAsServerAsync() + { + return Task.CompletedTask; + } /// /// Gets or sets the state. /// /// The state. - public WebSocketState State => WebSocket.ReadyState; + public WebSocketState State => WebSocket.State; /// /// Sends the async. @@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(bytes); + return WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); } /// @@ -93,7 +91,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(text); + return WebSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); } /// @@ -118,13 +116,10 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { - WebSocket.OnMessage -= OnSocketMessage; - WebSocket.OnClose -= OnSocketClose; - WebSocket.OnError -= OnSocketError; - _cancellationTokenSource.Cancel(); - WebSocket.CloseAsync().GetAwaiter().GetResult(); + // TODO + WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 824c9a822d..b64fc06833 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; @@ -144,22 +145,41 @@ using Microsoft.Extensions.Logging; _logger.LogDebug("Web socket connection allowed"); var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + var socket = new SharpWebSocket(webSocketContext, _logger); + await socket.ConnectAsServerAsync().ConfigureAwait(false); - if (WebSocketConnected != null) + WebSocketConnected(new WebSocketConnectEventArgs { - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); - //await socket.ConnectAsServerAsync().ConfigureAwait(false); - - //WebSocketConnected(new WebSocketConnectEventArgs - //{ - // Url = url, - // QueryString = queryString, - // WebSocket = socket, - // Endpoint = endpoint - //}); - - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); - } + Url = url, + QueryString = queryString, + WebSocket = socket, + Endpoint = endpoint + }); + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); + WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + socket.OnReceiveBytes(buffer.Array); + //while (!result.CloseStatus.HasValue) + //{ + // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); + + // result = await webSocketContext.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + //} + //WebSocketConnected?.Invoke(new WebSocketConnectEventArgs + //{ + // Url = url, + // QueryString = queryString, + // WebSocket = webSocketContext, + // Endpoint = endpoint + //}); + await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); + //await socket.ConnectAsServerAsync().ConfigureAwait(false); + + + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } else { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index c458fdb78f..4dc4103cb1 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -152,7 +152,7 @@ using Microsoft.Extensions.Logging; { Url = url, QueryString = queryString, - WebSocket = socket, + WebSocket = null, // socket, Endpoint = endpoint }); From 5a7cca9d1bdc5280626a6654cdbd8c0d45016af5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 19:48:18 +0100 Subject: [PATCH 09/59] Fix websockets and RawUrl --- .../SocketSharp/SharpWebSocket.cs | 2 +- .../SocketSharp/WebSocketSharpListener.cs | 13 +++++++++++-- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 89004ba7fe..eab903db8e 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.SocketSharp _cancellationTokenSource.Cancel(); // TODO - WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); + // WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index b64fc06833..05f159b4ee 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -160,6 +160,15 @@ using Microsoft.Extensions.Logging; var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); socket.OnReceiveBytes(buffer.Array); + + while (result.MessageType != WebSocketMessageType.Close) + { + result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + socket.OnReceiveBytes(buffer.Array); + } + await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + socket.Dispose(); + //while (!result.CloseStatus.HasValue) //{ // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); @@ -173,11 +182,11 @@ using Microsoft.Extensions.Logging; // WebSocket = webSocketContext, // Endpoint = endpoint //}); - await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + //await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); //await socket.ConnectAsServerAsync().ConfigureAwait(false); - + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 9c5b2b0834..b07c4bfebb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.SocketSharp public object Dto { get; set; } - public string RawUrl => request.Path.ToString(); + public string RawUrl => request.GetEncodedPathAndQuery(); public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); From a85488cd205532662c59ada5032dc81c0315543b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:13:48 +0100 Subject: [PATCH 10/59] Fix websockets array index out of bounds and some cleanup --- .../ApplicationHost.cs | 78 +------- .../HttpServer/HttpListenerHost.cs | 7 +- .../SocketSharp/WebSocketSharpListener.cs | 166 ++---------------- 3 files changed, 24 insertions(+), 227 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 65b6681c89..e9d8d8de7e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -624,25 +624,7 @@ namespace Emby.Server.Implementations Host = new WebHostBuilder() .UseKestrel() - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), @"jellyfin-web\src")) - //.UseStartup() -// .ConfigureServices(async services => -// { -// services.AddSingleton(startUp); -// RegisterResources(services); -// FindParts(); -// try -// { -// ImageProcessor.ImageEncoder = -// new NullImageEncoder(); //SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); -// } -// catch (Exception ex) -// { -// Logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); -// ImageProcessor.ImageEncoder = new NullImageEncoder(); -// } -// await RunStartupTasks().ConfigureAwait(false); -// }) + .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) .UseUrls("http://localhost:8096") .ConfigureServices(services => { @@ -672,63 +654,7 @@ namespace Emby.Server.Implementations return; } - await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(context).ConfigureAwait(false); - // try - // { - // var endpoint = ctx.Request.Path.ToString(); - // var url = ctx.Request.Path.ToString(); - - // var queryString = new QueryParamCollection(request.Query); - - // var connectingArgs = new WebSocketConnectingEventArgs - // { - // Url = url, - // QueryString = queryString, - // Endpoint = endpoint - // }; - - // if (connectingArgs.AllowConnection) - // { - // Logger.LogDebug("Web socket connection allowed"); - - // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - // //socket.ConnectAsServerAsync().ConfigureAwait(false); - - //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) - //// { - //// OnReceive = ProcessWebSocketMessageReceived, - //// Url = e.Url, - //// QueryString = e.QueryString ?? new QueryParamCollection() - //// }; - //// - //// connection.Closed += Connection_Closed; - //// - //// lock (_webSocketConnections) - //// { - //// _webSocketConnections.Add(connection); - //// } - //// - //// WebSocketConnected(new WebSocketConnectEventArgs - //// { - //// Url = url, - //// QueryString = queryString, - //// WebSocket = socket, - //// Endpoint = endpoint - //// }); - // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - // } - // else - // { - // Logger.LogWarning("Web socket connection not allowed"); - // ctx.Response.StatusCode = 401; - // } - // } - // catch (Exception ex) - // { - // ctx.Response.StatusCode = 500; - // } + await ((HttpListenerHost)HttpServer).ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index cfe0bbe974..a3eaa2fb50 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -21,6 +21,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -767,6 +768,10 @@ namespace Emby.Server.Implementations.HttpServer return _jsonSerializer.DeserializeFromStreamAsync(stream, type); } + public Task ProcessWebSocketRequest(HttpContext context) + { + return _websocketlistener.ProcessWebSocketRequest(context); + } //TODO Add Jellyfin Route Path Normalizer private static string NormalizeEmbyRoutePath(string path) @@ -801,7 +806,7 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); - public WebSocketSharpListener _websocketlistener; + private readonly WebSocketSharpListener _websocketlistener; protected virtual void Dispose(bool disposing) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 05f159b4ee..6e5190efdb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; - using System.Net; +using System.Linq; +using System.Net; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -38,94 +39,18 @@ using Microsoft.Extensions.Logging; 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) + private static void LogRequest(ILogger logger, HttpRequest request) { - var url = request.Url.ToString(); + var url = request.GetDisplayUrl(); - logger.LogInformation( - "{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, - url, - request.UserAgent ?? string.Empty); + logger.LogInformation("{0} {1}. UserAgent: {2}", "WS", url, request.Headers["User-Agent"].ToString()); } -// -// 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); -// } public async Task ProcessWebSocketRequest(HttpContext ctx) { try { + LogRequest(_logger, ctx.Request); var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); @@ -156,94 +81,35 @@ using Microsoft.Extensions.Logging; Endpoint = endpoint }); - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); - var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); - WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); - socket.OnReceiveBytes(buffer.Array); + var buffer = WebSocket.CreateClientBuffer(4096, 4096); + WebSocketReceiveResult result; + var message = new List(); - while (result.MessageType != WebSocketMessageType.Close) + do { result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); socket.OnReceiveBytes(buffer.Array); - } - await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); - socket.Dispose(); - - //while (!result.CloseStatus.HasValue) - //{ - // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - - // result = await webSocketContext.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - //} - //WebSocketConnected?.Invoke(new WebSocketConnectEventArgs - //{ - // Url = url, - // QueryString = queryString, - // WebSocket = webSocketContext, - // Endpoint = endpoint - //}); - //await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); - //await socket.ConnectAsServerAsync().ConfigureAwait(false); + message.AddRange(buffer.Array.Take(result.Count)); + } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); - - - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + socket.OnReceiveBytes(message.ToArray()); + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, CancellationToken.None); + socket.Dispose(); } else { _logger.LogWarning("Web socket connection not allowed"); ctx.Response.StatusCode = 401; - //ctx.Response.Close(); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); ctx.Response.StatusCode = 500; - //ctx.Response.Close(); - } - } - - private async Task ReceiveWebSocketAsync(HttpContext ctx, SharpWebSocket socket) - { - try - { - await socket.StartReceive().ConfigureAwait(false); - } - finally - { - TryClose(ctx, 200); - } - } - - private void TryClose(HttpContext ctx, int statusCode) - { - try - { - ctx.Response.StatusCode = statusCode; - } - catch (ObjectDisposedException) - { - // TODO: Investigate and properly fix. - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing web socket response"); } } - private IHttpRequest GetRequest(HttpRequest httpContext) - { - var urlSegments = httpContext.Path; - - var operationName = urlSegments; - - var req = new WebSocketSharpRequest(httpContext, httpContext.HttpContext.Response, operationName, _logger); - - return req; - } - public void Start(IEnumerable urlPrefixes) { throw new NotImplementedException(); From 4e8de67acabbfe1aeb5fee6d1162abc760fbd540 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:22:40 +0100 Subject: [PATCH 11/59] Remove SocketSharp from Jellyfin.Server and some other cleanup --- .../ApplicationHost.cs | 45 +- .../HttpServer/HttpListenerHost.cs | 15 +- Jellyfin.Server/CoreAppHost.cs | 4 - Jellyfin.Server/Jellyfin.Server.csproj | 2 +- Jellyfin.Server/SocketSharp/HttpFile.cs | 18 - Jellyfin.Server/SocketSharp/HttpPostedFile.cs | 204 ------ Jellyfin.Server/SocketSharp/RequestMono.cs | 680 ------------------ Jellyfin.Server/SocketSharp/SharpWebSocket.cs | 149 ---- .../SocketSharp/WebSocketSharpListener.cs | 237 ------ .../SocketSharp/WebSocketSharpRequest.cs | 535 -------------- .../SocketSharp/WebSocketSharpResponse.cs | 215 ------ 11 files changed, 11 insertions(+), 2093 deletions(-) delete mode 100644 Jellyfin.Server/SocketSharp/HttpFile.cs delete mode 100644 Jellyfin.Server/SocketSharp/HttpPostedFile.cs delete mode 100644 Jellyfin.Server/SocketSharp/RequestMono.cs delete mode 100644 Jellyfin.Server/SocketSharp/SharpWebSocket.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs 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); - } - } - } - } -} From e88f079da64a978d560f739e9bf17cd0a93d122a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:40:05 +0100 Subject: [PATCH 12/59] Remove websocket options and configure kestrel listen ports --- .../ApplicationHost.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ad6b8e84a8..7be39d6744 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -111,13 +111,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; -using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -623,21 +621,24 @@ namespace Emby.Server.Implementations FindParts(); Host = new WebHostBuilder() - .UseKestrel() + .UseKestrel(options => + { + options.Listen(IPAddress.Any, HttpPort); + options.Listen(IPAddress.Loopback, HttpPort); + // TODO certs + options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + }) .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) - .UseUrls("http://localhost:8096") .ConfigureServices(services => { services.AddResponseCompression(); services.AddHttpContextAccessor(); services.TryAddSingleton(); }) - .Configure( app => + .Configure(app => { - app.UseWebSockets(new WebSocketOptions { - KeepAliveInterval = TimeSpan.FromMilliseconds(1000000000), - ReceiveBufferSize = 0x10000 - }); + app.UseWebSockets(); app.UseResponseCompression(); app.Use(ExecuteWebsocketHandlerAsync); From 4e229ad86b3a3b6024ca0c29c17e4aac5386a210 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:50:10 +0100 Subject: [PATCH 13/59] Fix PathInfo --- .../SocketSharp/WebSocketSharpRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index b07c4bfebb..146862e02b 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -341,10 +341,10 @@ namespace Emby.Server.Implementations.SocketSharp { var mode = HandlerFactoryPath; - var pos = request.Path.ToString().IndexOf("?", StringComparison.Ordinal); + var pos = RawUrl.IndexOf("?", StringComparison.Ordinal); if (pos != -1) { - var path = request.Path.ToString().Substring(0, pos); + var path = RawUrl.Substring(0, pos); this.pathInfo = GetPathInfo( path, mode, @@ -352,10 +352,10 @@ namespace Emby.Server.Implementations.SocketSharp } else { - this.pathInfo = request.Path.ToString(); + this.pathInfo = RawUrl; } - this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); + this.pathInfo = WebUtility.UrlDecode(pathInfo); this.pathInfo = NormalizePathInfo(pathInfo, mode); } From f1c93ae618f293eae4cc384fadfd4440c4e0cf2d Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:08:30 +0100 Subject: [PATCH 14/59] Remove SetContentLength and company --- .../HttpClientManager/HttpClientManager.cs | 1 - .../HttpServer/FileWriter.cs | 3 --- .../HttpServer/HttpListenerHost.cs | 6 +----- .../HttpServer/HttpResultFactory.cs | 13 ++++--------- .../HttpServer/RangeRequestWriter.cs | 3 +-- .../HttpServer/ResponseFilter.cs | 1 - .../HttpServer/StreamWriter.cs | 10 +--------- Emby.Server.Implementations/Services/HttpResult.cs | 7 +------ .../Services/ResponseHelper.cs | 7 ------- .../SocketSharp/WebSocketSharpResponse.cs | 8 -------- MediaBrowser.Model/Services/IRequest.cs | 2 -- 11 files changed, 8 insertions(+), 53 deletions(-) diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 834a494f81..ef82d1d9ee 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -327,7 +327,6 @@ namespace Emby.Server.Implementations.HttpClientManager } httpWebRequest.ContentType = contentType; - // httpWebRequest.ContentLength = bytes.Length; (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); } catch (Exception ex) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 835c6f52ef..1375089e39 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { - // TODO - //Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); StatusCode = HttpStatusCode.OK; } else @@ -99,7 +97,6 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - // TODO Headers["Content-Length"] = lengthString; var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); Headers["Content-Range"] = rangeString; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 12c23a8693..6b69fa0f4e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -649,11 +649,6 @@ namespace Emby.Server.Implementations.HttpServer private static Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); - response.SetContentLength(bOutput.Length); - // TODO - response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID - response.SendChunked = true; - return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } @@ -672,6 +667,7 @@ namespace Emby.Server.Implementations.HttpServer } else { + // TODO what is this? var httpsUrl = url .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 5e9d2b4c36..09cdbc3c26 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty(); } - result = new StreamWriter(content, contentType, contentLength); + result = new StreamWriter(content, contentType); } else { @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty(); } - result = new StreamWriter(bytes, contentType, contentLength); + result = new StreamWriter(bytes, contentType); } else { @@ -334,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty(), contentType, contentLength); + var result = new StreamWriter(Array.Empty(), contentType); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType, contentLength); + var result = new StreamWriter(content, contentType); AddResponseHeaders(result, responseHeaders); return result; } @@ -590,11 +590,6 @@ namespace Emby.Server.Implementations.HttpServer } else { - if (totalContentLength.HasValue) - { - // TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); - } - if (isHeadRequest) { using (stream) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 9c8ab8d91c..8904e11d30 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,8 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - // TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture); - Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + Headers["Content-Range"] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) { diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index dbce3250d4..ae6a6576e0 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.HttpServer if (length > 0) { - res.SetContentLength(length); res.SendChunked = false; } } diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 750b0f7958..90354385dc 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -53,11 +53,6 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; Headers["Content-Type"] = contentType; - - if (source.CanSeek) - { - // TODO Headers["Content-Length"] = source.Length.ToString(UsCulture); - } } /// @@ -65,8 +60,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The source. /// Type of the content. - /// The logger. - public StreamWriter(byte[] source, string contentType, int contentLength) + public StreamWriter(byte[] source, string contentType) { if (string.IsNullOrEmpty(contentType)) { @@ -76,8 +70,6 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; Headers["Content-Type"] = contentType; - - // TODO Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 296da2f7a0..b6758486ce 100644 --- a/Emby.Server.Implementations/Services/HttpResult.cs +++ b/Emby.Server.Implementations/Services/HttpResult.cs @@ -43,14 +43,9 @@ namespace Emby.Server.Implementations.Services { var contentLength = bytesResponse.Length; - if (response != null) - { - response.SetContentLength(contentLength); - } - if (contentLength > 0) { - await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false); + await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false); } return; } diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs index dc99753477..0301ff335f 100644 --- a/Emby.Server.Implementations/Services/ResponseHelper.cs +++ b/Emby.Server.Implementations/Services/ResponseHelper.cs @@ -20,8 +20,6 @@ namespace Emby.Server.Implementations.Services { response.StatusCode = (int)HttpStatusCode.NoContent; } - - response.SetContentLength(0); return Task.CompletedTask; } @@ -55,7 +53,6 @@ namespace Emby.Server.Implementations.Services { if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase)) { - response.SetContentLength(long.Parse(responseHeaders.Value)); continue; } @@ -104,7 +101,6 @@ namespace Emby.Server.Implementations.Services if (bytes != null) { response.ContentType = "application/octet-stream"; - response.SetContentLength(bytes.Length); if (bytes.Length > 0) { @@ -117,7 +113,6 @@ namespace Emby.Server.Implementations.Services if (responseText != null) { bytes = Encoding.UTF8.GetBytes(responseText); - response.SetContentLength(bytes.Length); if (bytes.Length > 0) { return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken); @@ -149,8 +144,6 @@ namespace Emby.Server.Implementations.Services var contentLength = ms.Length; - response.SetContentLength(contentLength); - if (contentLength > 0) { await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index c68b959846..f9ecb52a55 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -143,14 +143,6 @@ namespace Emby.Server.Implementations.SocketSharp 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); diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 909cebce3d..b4ad24fec8 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -145,8 +145,6 @@ namespace MediaBrowser.Model.Services /// bool IsClosed { get; } - void SetContentLength(long contentLength); - //Add Metadata to Response Dictionary Items { get; } From e342b7bc71f16286da699bcce299b76df63360dc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:27:02 +0100 Subject: [PATCH 15/59] Extend the IHttpServer interface to avoid the typecasting --- .../ApplicationHost.cs | 4 ++-- .../HttpServer/IHttpListener.cs | 14 ------------ MediaBrowser.Controller/Net/IHttpServer.cs | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7be39d6744..a1ea179040 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -655,7 +655,7 @@ namespace Emby.Server.Implementations return; } - await ((HttpListenerHost)HttpServer).ProcessWebSocketRequest(context).ConfigureAwait(false); + await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { @@ -670,7 +670,7 @@ namespace Emby.Server.Implementations var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); + await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 8350913610..1ef65d9d73 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -1,9 +1,7 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; -using MediaBrowser.Controller.Net; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.HttpServer @@ -28,18 +26,6 @@ namespace Emby.Server.Implementations.HttpServer /// The web socket handler. Action WebSocketConnected { get; set; } - /// - /// Gets or sets the web socket connecting. - /// - /// The web socket connecting. - Action WebSocketConnecting { get; set; } - - /// - /// Starts this instance. - /// - /// The URL prefixes. - void Start(IEnumerable urlPrefixes); - /// /// Stops this instance. /// diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index f413030076..cede9a98fb 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Model.Events; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,5 +38,24 @@ namespace MediaBrowser.Controller.Net /// If set, all requests will respond with this message /// string GlobalResponse { get; set; } + + /// + /// Sends the http context to the socket listener + /// + /// + /// + Task ProcessWebSocketRequest(HttpContext ctx); + + /// + /// The HTTP request handler + /// + /// + /// + /// + /// + /// + /// + Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, + CancellationToken cancellationToken); } } From 148db8b81ac4bb52262e071e6920b1449c8611fe Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:51:02 +0100 Subject: [PATCH 16/59] Remove unused SharpSocket stuff --- .../SocketSharp/SharpWebSocket.cs | 44 +++---------------- .../SocketSharp/WebSocketSharpListener.cs | 1 - 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index eab903db8e..66c5cc334b 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Gets or sets the web socket. /// /// The web socket. - private WebSocket WebSocket { get; set; } + private readonly WebSocket _webSocket; private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); @@ -30,45 +30,14 @@ namespace Emby.Server.Implementations.SocketSharp public SharpWebSocket(WebSocket socket, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); + _webSocket = socket ?? throw new ArgumentNullException(nameof(socket)); } - 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(SocketHttpListener.MessageEventArgs e) - { - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - public Task ConnectAsServerAsync() - { - return Task.CompletedTask; - } /// /// Gets or sets the state. /// /// The state. - public WebSocketState State => WebSocket.State; + public WebSocketState State => _webSocket.State; /// /// Sends the async. @@ -79,7 +48,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); + return _webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); } /// @@ -91,7 +60,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); + return _webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); } /// @@ -117,9 +86,6 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { _cancellationTokenSource.Cancel(); - - // TODO - // WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 6e5190efdb..7a6144fb23 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -71,7 +71,6 @@ using Microsoft.Extensions.Logging; var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); var socket = new SharpWebSocket(webSocketContext, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs { From 9c02e99e35fa6811502944b19dd1479750ff8f20 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:40:25 +0100 Subject: [PATCH 17/59] Undo some of the span abuse --- .../ApplicationHost.cs | 2 +- .../Emby.Server.Implementations.csproj | 1 + .../SocketSharp/WebSocketSharpRequest.cs | 25 ++++++------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a1ea179040..0b4a2fd30e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1676,7 +1676,7 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = logPing, LogRequest = logPing, - TimeoutMs = 30000, + TimeoutMs = 5000, BufferContent = false, CancellationToken = cancellationToken diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index a4dac4a7f9..230e4892c7 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -40,6 +40,7 @@ + diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 146862e02b..35fff08216 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -273,11 +273,11 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - string format = httpReq.Query["format"]; + ReadOnlySpan format = httpReq.Query["format"].ToString().AsSpan(); if (format == null) { const int formatMaxLength = 4; - string pi = httpReq.Path.ToString(); + ReadOnlySpan pi = httpReq.Path.ToString().AsSpan(); if (pi == null || pi.Length <= formatMaxLength) { return null; @@ -285,7 +285,7 @@ namespace Emby.Server.Implementations.SocketSharp if (pi[0] == '/') { - pi = pi.Substring(1); + pi = pi.Slice(1); } format = LeftPart(pi, '/'); @@ -296,11 +296,11 @@ namespace Emby.Server.Implementations.SocketSharp } format = LeftPart(format, '.'); - if (format.ToLower().Contains("json")) + if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return "application/json"; } - else if (format.ToLower().Contains("xml")) + else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return "application/xml"; } @@ -308,25 +308,14 @@ namespace Emby.Server.Implementations.SocketSharp return null; } - public static string LeftPart(string strVal, char needle) + public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) { if (strVal == null) { return null; } - var pos = strVal.IndexOf(needle.ToString(), 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.ToString()); + var pos = strVal.IndexOf(needle); return pos == -1 ? strVal : strVal.Slice(0, pos); } From 5510e8ebee743a3d07571051660b514d6cce973c Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:53:59 +0100 Subject: [PATCH 18/59] Remove unused Cookies --- .../SocketSharp/WebSocketSharpRequest.cs | 19 ------------------- MediaBrowser.Model/Services/IRequest.cs | 2 -- 2 files changed, 21 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 35fff08216..38d0332308 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -412,25 +412,6 @@ namespace Emby.Server.Implementations.SocketSharp 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 = cookie; - cookies[httpCookie.Key] = new Cookie(httpCookie.Key, httpCookie.Value, "", ""); - } - } - - return cookies; - } - } - public string UserAgent => request.Headers[HeaderNames.UserAgent]; public QueryParamCollection Headers => new QueryParamCollection(request.Headers); diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index b4ad24fec8..c7dccdb6fd 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.Model.Services string UserAgent { get; } - IDictionary Cookies { get; } - /// /// The expected Response ContentType for this request /// From 848cfc32cc89327e16ff6ea281dc1d9b96cc4f7a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:57:59 +0100 Subject: [PATCH 19/59] More cleanup --- .../HttpServer/HttpListenerHost.cs | 1 - .../SocketSharp/WebSocketSharpListener.cs | 13 ++----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 6b69fa0f4e..5b1cb433a9 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -803,7 +803,6 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); - private readonly WebSocketSharpListener _websocketlistener; protected virtual void Dispose(bool disposing) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 7a6144fb23..77469244b9 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -17,8 +17,6 @@ using Microsoft.Extensions.Logging; { public class WebSocketSharpListener : IHttpListener { - private HttpListener _listener; - private readonly ILogger _logger; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); @@ -86,14 +84,14 @@ using Microsoft.Extensions.Logging; do { - result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); socket.OnReceiveBytes(buffer.Array); message.AddRange(buffer.Array.Take(result.Count)); } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); socket.OnReceiveBytes(message.ToArray()); await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, CancellationToken.None); + result.CloseStatusDescription, _disposeCancellationToken); socket.Dispose(); } else @@ -109,16 +107,9 @@ using Microsoft.Extensions.Logging; } } - public void Start(IEnumerable urlPrefixes) - { - throw new NotImplementedException(); - } - public Task Stop() { _disposeCancellationTokenSource.Cancel(); - _listener?.Close(); - return Task.CompletedTask; } From 77addb22835478a32c1133cfd69ae0da2ec5edea Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 07:32:36 +0100 Subject: [PATCH 20/59] Remove SocketHttpListener --- .../Emby.Server.Implementations.csproj | 2 - .../SocketSharp/SharpWebSocket.cs | 3 +- .../SocketSharp/WebSocketSharpRequest.cs | 1 - MediaBrowser.sln | 379 ++++--- SocketHttpListener/ByteOrder.cs | 17 - SocketHttpListener/CloseEventArgs.cs | 79 -- SocketHttpListener/CloseStatusCode.cs | 94 -- SocketHttpListener/CompressionMethod.cs | 23 - SocketHttpListener/ErrorEventArgs.cs | 42 - SocketHttpListener/Ext.cs | 946 ------------------ SocketHttpListener/Fin.cs | 8 - SocketHttpListener/HttpBase.cs | 70 -- SocketHttpListener/HttpResponse.cs | 122 --- SocketHttpListener/Mask.cs | 8 - SocketHttpListener/MessageEventArgs.cs | 84 -- SocketHttpListener/Net/CookieHelper.cs | 141 --- SocketHttpListener/Opcode.cs | 43 - SocketHttpListener/PayloadData.cs | 130 --- SocketHttpListener/Properties/AssemblyInfo.cs | 21 - SocketHttpListener/Rsv.cs | 8 - SocketHttpListener/SocketHttpListener.csproj | 18 - SocketHttpListener/SocketStream.cs | 74 -- SocketHttpListener/WebSocket.cs | 777 -------------- SocketHttpListener/WebSocketException.cs | 61 -- SocketHttpListener/WebSocketFrame.cs | 432 -------- 25 files changed, 188 insertions(+), 3395 deletions(-) delete mode 100644 SocketHttpListener/ByteOrder.cs delete mode 100644 SocketHttpListener/CloseEventArgs.cs delete mode 100644 SocketHttpListener/CloseStatusCode.cs delete mode 100644 SocketHttpListener/CompressionMethod.cs delete mode 100644 SocketHttpListener/ErrorEventArgs.cs delete mode 100644 SocketHttpListener/Ext.cs delete mode 100644 SocketHttpListener/Fin.cs delete mode 100644 SocketHttpListener/HttpBase.cs delete mode 100644 SocketHttpListener/HttpResponse.cs delete mode 100644 SocketHttpListener/Mask.cs delete mode 100644 SocketHttpListener/MessageEventArgs.cs delete mode 100644 SocketHttpListener/Net/CookieHelper.cs delete mode 100644 SocketHttpListener/Opcode.cs delete mode 100644 SocketHttpListener/PayloadData.cs delete mode 100644 SocketHttpListener/Properties/AssemblyInfo.cs delete mode 100644 SocketHttpListener/Rsv.cs delete mode 100644 SocketHttpListener/SocketHttpListener.csproj delete mode 100644 SocketHttpListener/SocketStream.cs delete mode 100644 SocketHttpListener/WebSocket.cs delete mode 100644 SocketHttpListener/WebSocketException.cs delete mode 100644 SocketHttpListener/WebSocketFrame.cs diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 230e4892c7..d4db5f2fcc 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -9,7 +9,6 @@ - @@ -40,7 +39,6 @@ - diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 66c5cc334b..dcbfb80487 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.SocketSharp /// The web socket. private readonly WebSocket _webSocket; - private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed = false; + private bool _disposed; public SharpWebSocket(WebSocket socket, ILogger logger) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 38d0332308..53dce667ba 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using SocketHttpListener.Net; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 62ae58d732..5edc1a25a6 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,192 +1,187 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{1D74413B-E7CF-455B-B021-F52BDF881542}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - SharedVersion.cs = SharedVersion.cs - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.Build.0 = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} - EndGlobalSection - GlobalSection(AutomaticVersions) = postSolution - UpdateAssemblyVersion = True - UpdateAssemblyFileVersion = True - UpdateAssemblyInfoVersion = True - AssemblyVersionSettings = None.None.None.None - AssemblyFileVersionSettings = None.None.None.None - AssemblyInfoVersionSettings = None.None.None.None - UpdatePackageVersion = False - AssemblyInfoVersionType = SettingsVersion - InheritWinAppVersionFrom = None - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n - $0.DotNetNamingPolicy = $2 - $2.DirectoryNamespaceAssociation = PrefixedHierarchical - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal diff --git a/SocketHttpListener/ByteOrder.cs b/SocketHttpListener/ByteOrder.cs deleted file mode 100644 index c04150c74b..0000000000 --- a/SocketHttpListener/ByteOrder.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values that indicate whether the byte order is a Little-endian or Big-endian. - /// - public enum ByteOrder : byte - { - /// - /// Indicates a Little-endian. - /// - Little, - /// - /// Indicates a Big-endian. - /// - Big - } -} diff --git a/SocketHttpListener/CloseEventArgs.cs b/SocketHttpListener/CloseEventArgs.cs deleted file mode 100644 index c6460fd230..0000000000 --- a/SocketHttpListener/CloseEventArgs.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Text; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the WebSocket connection has been closed. - /// If you would like to get the reason for the close, you should access the or - /// property. - /// - public class CloseEventArgs : EventArgs - { - #region Private Fields - - private bool _clean; - private ushort _code; - private string _reason; - - #endregion - - #region Internal Constructors - - internal CloseEventArgs(PayloadData payload) - { - var data = payload.ApplicationData; - var len = data.Length; - _code = len > 1 - ? data.SubArray(0, 2).ToUInt16(ByteOrder.Big) - : (ushort)CloseStatusCode.NoStatusCode; - - _reason = len > 2 - ? GetUtf8String(data.SubArray(2, len - 2)) - : string.Empty; - } - - private static string GetUtf8String(byte[] bytes) - { - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - - #endregion - - #region Public Properties - - /// - /// Gets the status code for the close. - /// - /// - /// A that represents the status code for the close if any. - /// - public ushort Code => _code; - - /// - /// Gets the reason for the close. - /// - /// - /// A that represents the reason for the close if any. - /// - public string Reason => _reason; - - /// - /// Gets a value indicating whether the WebSocket connection has been closed cleanly. - /// - /// - /// true if the WebSocket connection has been closed cleanly; otherwise, false. - /// - public bool WasClean - { - get => _clean; - - internal set => _clean = value; - } - - #endregion - } -} diff --git a/SocketHttpListener/CloseStatusCode.cs b/SocketHttpListener/CloseStatusCode.cs deleted file mode 100644 index 428595bb09..0000000000 --- a/SocketHttpListener/CloseStatusCode.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the status code for the WebSocket connection close. - /// - /// - /// - /// The values of the status code are defined in - /// Section 7.4 - /// of RFC 6455. - /// - /// - /// "Reserved value" must not be set as a status code in a close control frame - /// by an endpoint. It's designated for use in applications expecting a status - /// code to indicate that the connection was closed due to the system grounds. - /// - /// - public enum CloseStatusCode : ushort - { - /// - /// Equivalent to close status 1000. - /// Indicates a normal close. - /// - Normal = 1000, - /// - /// Equivalent to close status 1001. - /// Indicates that an endpoint is going away. - /// - Away = 1001, - /// - /// Equivalent to close status 1002. - /// Indicates that an endpoint is terminating the connection due to a protocol error. - /// - ProtocolError = 1002, - /// - /// Equivalent to close status 1003. - /// Indicates that an endpoint is terminating the connection because it has received - /// an unacceptable type message. - /// - IncorrectData = 1003, - /// - /// Equivalent to close status 1004. - /// Still undefined. Reserved value. - /// - Undefined = 1004, - /// - /// Equivalent to close status 1005. - /// Indicates that no status code was actually present. Reserved value. - /// - NoStatusCode = 1005, - /// - /// Equivalent to close status 1006. - /// Indicates that the connection was closed abnormally. Reserved value. - /// - Abnormal = 1006, - /// - /// Equivalent to close status 1007. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that contains a data that isn't consistent with the type of the message. - /// - InconsistentData = 1007, - /// - /// Equivalent to close status 1008. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that violates its policy. - /// - PolicyViolation = 1008, - /// - /// Equivalent to close status 1009. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that is too big to process. - /// - TooBig = 1009, - /// - /// Equivalent to close status 1010. - /// Indicates that the client is terminating the connection because it has expected - /// the server to negotiate one or more extension, but the server didn't return them - /// in the handshake response. - /// - IgnoreExtension = 1010, - /// - /// Equivalent to close status 1011. - /// Indicates that the server is terminating the connection because it has encountered - /// an unexpected condition that prevented it from fulfilling the request. - /// - ServerError = 1011, - /// - /// Equivalent to close status 1015. - /// Indicates that the connection was closed due to a failure to perform a TLS handshake. - /// Reserved value. - /// - TlsHandshakeFailure = 1015 - } -} diff --git a/SocketHttpListener/CompressionMethod.cs b/SocketHttpListener/CompressionMethod.cs deleted file mode 100644 index d6bcd63d89..0000000000 --- a/SocketHttpListener/CompressionMethod.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the compression method used to compress the message on the WebSocket - /// connection. - /// - /// - /// The values of the compression method are defined in - /// Compression - /// Extensions for WebSocket. - /// - public enum CompressionMethod : byte - { - /// - /// Indicates non compression. - /// - None, - /// - /// Indicates using DEFLATE. - /// - Deflate - } -} diff --git a/SocketHttpListener/ErrorEventArgs.cs b/SocketHttpListener/ErrorEventArgs.cs deleted file mode 100644 index 9502d2a156..0000000000 --- a/SocketHttpListener/ErrorEventArgs.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the gets an error. - /// If you would like to get the error message, you should access the - /// property. - /// - public class ErrorEventArgs : EventArgs - { - #region Private Fields - - private string _message; - - #endregion - - #region Internal Constructors - - internal ErrorEventArgs(string message) - { - _message = message; - } - - #endregion - - #region Public Properties - - /// - /// Gets the error message. - /// - /// - /// A that represents the error message. - /// - public string Message => _message; - - #endregion - } -} diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs deleted file mode 100644 index 697c5d5b70..0000000000 --- a/SocketHttpListener/Ext.cs +++ /dev/null @@ -1,946 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener -{ - /// - /// Provides a set of static methods for the websocket-sharp. - /// - public static class Ext - { - #region Private Const Fields - - private const string _tspecials = "()<>@,;:\\\"/[]?={} \t"; - - #endregion - - #region Private Methods - - private static MemoryStream compress(this Stream stream) - { - var output = new MemoryStream(); - if (stream.Length == 0) - return output; - - stream.Position = 0; - using (var ds = new DeflateStream(output, CompressionMode.Compress, true)) - { - stream.CopyTo(ds); - //ds.Close(); // "BFINAL" set to 1. - output.Position = 0; - - return output; - } - } - - private static byte[] decompress(this byte[] value) - { - if (value.Length == 0) - return value; - - using (var input = new MemoryStream(value)) - { - return input.decompressToArray(); - } - } - - private static MemoryStream decompress(this Stream stream) - { - var output = new MemoryStream(); - if (stream.Length == 0) - return output; - - stream.Position = 0; - using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true)) - { - ds.CopyTo(output, true); - return output; - } - } - - private static byte[] decompressToArray(this Stream stream) - { - using (var decomp = stream.decompress()) - { - return decomp.ToArray(); - } - } - - private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length) - { - var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false); - if (len < 1) - return buffer.SubArray(0, offset); - - var tmp = 0; - while (len < length) - { - tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false); - if (tmp < 1) - { - break; - } - - len += tmp; - } - - return len < length - ? buffer.SubArray(0, offset + len) - : buffer; - } - - private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest) - { - var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false); - var len = bytes.Length; - dest.Write(bytes, 0, len); - - return len == offset + length; - } - - #endregion - - #region Internal Methods - - internal static async Task AppendAsync(this ushort code, string reason) - { - using (var buffer = new MemoryStream()) - { - var tmp = code.ToByteArrayInternally(ByteOrder.Big); - await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false); - if (reason != null && reason.Length > 0) - { - tmp = Encoding.UTF8.GetBytes(reason); - await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false); - } - - return buffer.ToArray(); - } - } - - internal static string CheckIfClosable(this WebSocketState state) - { - return state == WebSocketState.CloseSent - ? "While closing the WebSocket connection." - : state == WebSocketState.Closed - ? "The WebSocket connection has already been closed." - : null; - } - - internal static string CheckIfOpen(this WebSocketState state) - { - return state == WebSocketState.Connecting - ? "A WebSocket connection isn't established." - : state == WebSocketState.CloseSent - ? "While closing the WebSocket connection." - : state == WebSocketState.Closed - ? "The WebSocket connection has already been closed." - : null; - } - - internal static string CheckIfValidControlData(this byte[] data, string paramName) - { - return data.Length > 125 - ? string.Format("'{0}' length must be less.", paramName) - : null; - } - - internal static Stream Compress(this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.compress() - : stream; - } - - internal static bool Contains(this IEnumerable source, Func condition) - { - foreach (T elm in source) - if (condition(elm)) - return true; - - return false; - } - - internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition) - { - var readLen = 0; - var bufferLen = 256; - var buffer = new byte[bufferLen]; - while ((readLen = src.Read(buffer, 0, bufferLen)) > 0) - { - dest.Write(buffer, 0, readLen); - } - - if (setDefaultPosition) - dest.Position = 0; - } - - internal static byte[] Decompress(this byte[] value, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? value.decompress() - : value; - } - - internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.decompressToArray() - : stream.ToByteArray(); - } - - /// - /// Determines whether the specified equals the specified , - /// and invokes the specified Action<int> delegate at the same time. - /// - /// - /// true if equals ; - /// otherwise, false. - /// - /// - /// An to compare. - /// - /// - /// A to compare. - /// - /// - /// An Action<int> delegate that references the method(s) called at - /// the same time as comparing. An parameter to pass to - /// the method(s) is . - /// - /// - /// isn't between 0 and 255. - /// - internal static bool EqualsWith(this int value, char c, Action action) - { - if (value < 0 || value > 255) - throw new ArgumentOutOfRangeException(nameof(value)); - - action(value); - return value == c - 0; - } - - internal static string GetMessage(this CloseStatusCode code) - { - return code == CloseStatusCode.ProtocolError - ? "A WebSocket protocol error has occurred." - : code == CloseStatusCode.IncorrectData - ? "An incorrect data has been received." - : code == CloseStatusCode.Abnormal - ? "An exception has occurred." - : code == CloseStatusCode.InconsistentData - ? "An inconsistent data has been received." - : code == CloseStatusCode.PolicyViolation - ? "A policy violation has occurred." - : code == CloseStatusCode.TooBig - ? "A too big data has been received." - : code == CloseStatusCode.IgnoreExtension - ? "WebSocket client did not receive expected extension(s)." - : code == CloseStatusCode.ServerError - ? "WebSocket server got an internal error." - : code == CloseStatusCode.TlsHandshakeFailure - ? "An error has occurred while handshaking." - : string.Empty; - } - - internal static string GetNameInternal(this string nameAndValue, string separator) - { - var i = nameAndValue.IndexOf(separator); - return i > 0 - ? nameAndValue.Substring(0, i).Trim() - : null; - } - - internal static string GetValueInternal(this string nameAndValue, string separator) - { - var i = nameAndValue.IndexOf(separator); - return i >= 0 && i < nameAndValue.Length - 1 - ? nameAndValue.Substring(i + 1).Trim() - : null; - } - - internal static bool IsCompressionExtension(this string value, CompressionMethod method) - { - return value.StartsWith(method.ToExtensionString()); - } - - internal static bool IsPortNumber(this int value) - { - return value > 0 && value < 65536; - } - - internal static bool IsReserved(this ushort code) - { - return code == (ushort)CloseStatusCode.Undefined || - code == (ushort)CloseStatusCode.NoStatusCode || - code == (ushort)CloseStatusCode.Abnormal || - code == (ushort)CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsReserved(this CloseStatusCode code) - { - return code == CloseStatusCode.Undefined || - code == CloseStatusCode.NoStatusCode || - code == CloseStatusCode.Abnormal || - code == CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsText(this string value) - { - var len = value.Length; - for (var i = 0; i < len; i++) - { - char c = value[i]; - if (c < 0x20 && !"\r\n\t".Contains(c)) - return false; - - if (c == 0x7f) - return false; - - if (c == '\n' && ++i < len) - { - c = value[i]; - if (!" \t".Contains(c)) - return false; - } - } - - return true; - } - - internal static bool IsToken(this string value) - { - foreach (char c in value) - if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c)) - return false; - - return true; - } - - internal static string Quote(this string value) - { - return value.IsToken() - ? value - : string.Format("\"{0}\"", value.Replace("\"", "\\\"")); - } - - internal static Task ReadBytesAsync(this Stream stream, int length) - => stream.ReadBytesAsync(new byte[length], 0, length); - - internal static async Task ReadBytesAsync(this Stream stream, long length, int bufferLength) - { - using (var result = new MemoryStream()) - { - var count = length / bufferLength; - var rem = (int)(length % bufferLength); - - var buffer = new byte[bufferLength]; - var end = false; - for (long i = 0; i < count; i++) - { - if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false)) - { - end = true; - break; - } - } - - if (!end && rem > 0) - { - await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false); - } - - return result.ToArray(); - } - } - - internal static string RemovePrefix(this string value, params string[] prefixes) - { - var i = 0; - foreach (var prefix in prefixes) - { - if (value.StartsWith(prefix)) - { - i = prefix.Length; - break; - } - } - - return i > 0 - ? value.Substring(i) - : value; - } - - internal static T[] Reverse(this T[] array) - { - var len = array.Length; - T[] reverse = new T[len]; - - var end = len - 1; - for (var i = 0; i <= end; i++) - reverse[i] = array[end - i]; - - return reverse; - } - - internal static IEnumerable SplitHeaderValue( - this string value, params char[] separator) - { - var len = value.Length; - var separators = new string(separator); - - var buffer = new StringBuilder(32); - var quoted = false; - var escaped = false; - - char c; - for (var i = 0; i < len; i++) - { - c = value[i]; - if (c == '"') - { - if (escaped) - escaped = !escaped; - else - quoted = !quoted; - } - else if (c == '\\') - { - if (i < len - 1 && value[i + 1] == '"') - escaped = true; - } - else if (separators.Contains(c)) - { - if (!quoted) - { - yield return buffer.ToString(); - buffer.Length = 0; - - continue; - } - } - else - { - } - - buffer.Append(c); - } - - if (buffer.Length > 0) - yield return buffer.ToString(); - } - - internal static byte[] ToByteArray(this Stream stream) - { - using (var output = new MemoryStream()) - { - stream.Position = 0; - stream.CopyTo(output); - - return output.ToArray(); - } - } - - internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order) - { - var bytes = BitConverter.GetBytes(value); - if (!order.IsHostOrder()) - Array.Reverse(bytes); - - return bytes; - } - - internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order) - { - var bytes = BitConverter.GetBytes(value); - if (!order.IsHostOrder()) - Array.Reverse(bytes); - - return bytes; - } - - internal static string ToExtensionString( - this CompressionMethod method, params string[] parameters) - { - if (method == CompressionMethod.None) - return string.Empty; - - var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant()); - if (parameters == null || parameters.Length == 0) - return m; - - return string.Format("{0}; {1}", m, parameters.ToString("; ")); - } - - internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder) - { - src.ToHostOrder(srcOrder); - return BitConverter.ToUInt16(src, 0); - } - - internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder) - { - src.ToHostOrder(srcOrder); - return BitConverter.ToUInt64(src, 0); - } - - internal static string TrimEndSlash(this string value) - { - value = value.TrimEnd('/'); - return value.Length > 0 - ? value - : "/"; - } - - internal static string Unquote(this string value) - { - var start = value.IndexOf('\"'); - var end = value.LastIndexOf('\"'); - if (start < end) - value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\""); - - return value.Trim(); - } - - internal static void WriteBytes(this Stream stream, byte[] value) - { - using (var src = new MemoryStream(value)) - { - src.CopyTo(stream); - } - } - - #endregion - - #region Public Methods - - /// - /// Determines whether the specified contains any of characters - /// in the specified array of . - /// - /// - /// true if contains any of ; - /// otherwise, false. - /// - /// - /// A to test. - /// - /// - /// An array of that contains characters to find. - /// - public static bool Contains(this string value, params char[] chars) - { - return chars == null || chars.Length == 0 - ? true - : value == null || value.Length == 0 - ? false - : value.IndexOfAny(chars) != -1; - } - - /// - /// Determines whether the specified contains the entry - /// with the specified . - /// - /// - /// true if contains the entry - /// with ; otherwise, false. - /// - /// - /// A to test. - /// - /// - /// A that represents the key of the entry to find. - /// - public static bool Contains(this QueryParamCollection collection, string name) - { - return collection == null || collection.Count == 0 - ? false - : collection[name] != null; - } - - /// - /// Determines whether the specified contains the entry - /// with the specified both and . - /// - /// - /// true if contains the entry - /// with both and ; - /// otherwise, false. - /// - /// - /// A to test. - /// - /// - /// A that represents the key of the entry to find. - /// - /// - /// A that represents the value of the entry to find. - /// - public static bool Contains(this QueryParamCollection collection, string name, string value) - { - if (collection == null || collection.Count == 0) - return false; - - var values = collection[name]; - if (values == null) - return false; - - foreach (var v in values.Split(',')) - if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase)) - return true; - - return false; - } - - /// - /// Emits the specified EventHandler<TEventArgs> delegate - /// if it isn't . - /// - /// - /// An EventHandler<TEventArgs> to emit. - /// - /// - /// An from which emits this . - /// - /// - /// A TEventArgs that represents the event data. - /// - /// - /// The type of the event data generated by the event. - /// - public static void Emit( - this EventHandler eventHandler, object sender, TEventArgs e) - where TEventArgs : EventArgs - { - if (eventHandler != null) - eventHandler(sender, e); - } - - /// - /// Gets the description of the specified HTTP status . - /// - /// - /// A that represents the description of the HTTP status code. - /// - /// - /// One of enum values, indicates the HTTP status codes. - /// - public static string GetDescription(this HttpStatusCode code) - { - return ((int)code).GetStatusDescription(); - } - - /// - /// Gets the description of the specified HTTP status . - /// - /// - /// A that represents the description of the HTTP status code. - /// - /// - /// An that represents the HTTP status code. - /// - public static string GetStatusDescription(this 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 string.Empty; - } - - /// - /// Determines whether the specified is host - /// (this computer architecture) byte order. - /// - /// - /// true if is host byte order; - /// otherwise, false. - /// - /// - /// One of the enum values, to test. - /// - public static bool IsHostOrder(this ByteOrder order) - { - // true : !(true ^ true) or !(false ^ false) - // false: !(true ^ false) or !(false ^ true) - return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little)); - } - - /// - /// Determines whether the specified is a predefined scheme. - /// - /// - /// true if is a predefined scheme; otherwise, false. - /// - /// - /// A to test. - /// - public static bool IsPredefinedScheme(this string value) - { - if (value == null || value.Length < 2) - return false; - - var c = value[0]; - if (c == 'h') - return value == "http" || value == "https"; - - if (c == 'w') - return value == "ws" || value == "wss"; - - if (c == 'f') - return value == "file" || value == "ftp"; - - if (c == 'n') - { - c = value[1]; - return c == 'e' - ? value == "news" || value == "net.pipe" || value == "net.tcp" - : value == "nntp"; - } - - return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto"); - } - - /// - /// Determines whether the specified is a URI string. - /// - /// - /// true if may be a URI string; otherwise, false. - /// - /// - /// A to test. - /// - public static bool MaybeUri(this string value) - { - if (value == null || value.Length == 0) - return false; - - var i = value.IndexOf(':'); - if (i == -1) - return false; - - if (i >= 10) - return false; - - return value.Substring(0, i).IsPredefinedScheme(); - } - - /// - /// Retrieves a sub-array from the specified . - /// A sub-array starts at the specified element position. - /// - /// - /// An array of T that receives a sub-array, or an empty array of T if any problems - /// with the parameters. - /// - /// - /// An array of T that contains the data to retrieve a sub-array. - /// - /// - /// An that contains the zero-based starting position of a sub-array - /// in . - /// - /// - /// An that contains the number of elements to retrieve a sub-array. - /// - /// - /// The type of elements in the . - /// - public static T[] SubArray(this T[] array, int startIndex, int length) - { - if (array == null || array.Length == 0) - return new T[0]; - - if (startIndex < 0 || length <= 0) - return new T[0]; - - if (startIndex + length > array.Length) - return new T[0]; - - if (startIndex == 0 && array.Length == length) - return array; - - T[] subArray = new T[length]; - Array.Copy(array, startIndex, subArray, 0, length); - - return subArray; - } - - /// - /// Converts the order of the specified array of to the host byte order. - /// - /// - /// An array of converted from . - /// - /// - /// An array of to convert. - /// - /// - /// One of the enum values, indicates the byte order of - /// . - /// - /// - /// is . - /// - public static void ToHostOrder(this byte[] src, ByteOrder srcOrder) - { - if (src == null) - { - throw new ArgumentNullException(nameof(src)); - } - - if (src.Length > 1 && !srcOrder.IsHostOrder()) - { - Array.Reverse(src); - } - } - - /// - /// Converts the specified to a that - /// concatenates the each element of across the specified - /// . - /// - /// - /// A converted from , - /// or if is empty. - /// - /// - /// An array of T to convert. - /// - /// - /// A that represents the separator string. - /// - /// - /// The type of elements in . - /// - /// - /// is . - /// - public static string ToString(this T[] array, string separator) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - - var len = array.Length; - if (len == 0) - return string.Empty; - - if (separator == null) - separator = string.Empty; - - var buff = new StringBuilder(64); - (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator)); - - buff.Append(array[len - 1].ToString()); - return buff.ToString(); - } - - /// - /// Executes the specified Action<int> delegate times. - /// - /// - /// An is the number of times to execute. - /// - /// - /// An Action<int> delegate that references the method(s) to execute. - /// An parameter to pass to the method(s) is the zero-based count of - /// iteration. - /// - public static void Times(this int n, Action action) - { - if (n > 0 && action != null) - for (int i = 0; i < n; i++) - action(i); - } - - /// - /// Converts the specified to a . - /// - /// - /// A converted from , or - /// if isn't successfully converted. - /// - /// - /// A to convert. - /// - public static Uri ToUri(this string uriString) - { - return Uri.TryCreate( - uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res) - ? res - : null; - } - - /// - /// URL-decodes the specified . - /// - /// - /// A that receives the decoded string, or the - /// if it's or empty. - /// - /// - /// A to decode. - /// - public static string UrlDecode(this string value) - { - return value == null || value.Length == 0 - ? value - : WebUtility.UrlDecode(value); - } - - #endregion - } -} diff --git a/SocketHttpListener/Fin.cs b/SocketHttpListener/Fin.cs deleted file mode 100644 index c8ffbf2b2f..0000000000 --- a/SocketHttpListener/Fin.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Fin : byte - { - More = 0x0, - Final = 0x1 - } -} diff --git a/SocketHttpListener/HttpBase.cs b/SocketHttpListener/HttpBase.cs deleted file mode 100644 index c386b93744..0000000000 --- a/SocketHttpListener/HttpBase.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener -{ - internal abstract class HttpBase - { - #region Private Fields - - private QueryParamCollection _headers; - private Version _version; - - #endregion - - #region Protected Fields - - protected const string CrLf = "\r\n"; - - #endregion - - #region Protected Constructors - - protected HttpBase(Version version, QueryParamCollection headers) - { - _version = version; - _headers = headers; - } - - #endregion - - #region Public Properties - - public QueryParamCollection Headers => _headers; - - public Version ProtocolVersion => _version; - - #endregion - - #region Private Methods - - private static Encoding getEncoding(string contentType) - { - if (contentType == null || contentType.Length == 0) - return Encoding.UTF8; - - var i = contentType.IndexOf("charset=", StringComparison.Ordinal); - if (i == -1) - return Encoding.UTF8; - - var charset = contentType.Substring(i + 8); - i = charset.IndexOf(';'); - if (i != -1) - charset = charset.Substring(0, i).TrimEnd(); - - return Encoding.GetEncoding(charset.Trim('"')); - } - - #endregion - - #region Public Methods - - public byte[] ToByteArray() - { - return Encoding.UTF8.GetBytes(ToString()); - } - - #endregion - } -} diff --git a/SocketHttpListener/HttpResponse.cs b/SocketHttpListener/HttpResponse.cs deleted file mode 100644 index a33b942959..0000000000 --- a/SocketHttpListener/HttpResponse.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net; - -namespace SocketHttpListener -{ - // TODO what is the point of this class? - internal class HttpResponse : HttpBase - { - #region Private Fields - - private string _code; - private string _reason; - - #endregion - - #region Private Constructors - - private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) - : base(version, headers) - { - _code = code; - _reason = reason; - } - - #endregion - - #region Internal Constructors - - internal HttpResponse(HttpStatusCode code) - : this(code, code.GetDescription()) - { - } - - internal HttpResponse(HttpStatusCode code, string reason) - : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) - { - Headers["Server"] = "websocket-sharp/1.0"; - } - - #endregion - - #region Public Properties - - public CookieCollection Cookies => GetCookies(Headers, true); - - private static CookieCollection GetCookies(QueryParamCollection headers, bool response) - { - var name = response ? "Set-Cookie" : "Cookie"; - return headers == null || !headers.Contains(name) - ? new CookieCollection() - : CookieHelper.Parse(headers[name], response); - } - - public bool IsProxyAuthenticationRequired => _code == "407"; - - public bool IsUnauthorized => _code == "401"; - - public bool IsWebSocketResponse - { - get - { - var headers = Headers; - return ProtocolVersion > HttpVersion.Version10 && - _code == "101" && - headers.Contains("Upgrade", "websocket") && - headers.Contains("Connection", "Upgrade"); - } - } - - public string Reason => _reason; - - public string StatusCode => _code; - - #endregion - - #region Internal Methods - - internal static HttpResponse CreateCloseResponse(HttpStatusCode code) - { - var res = new HttpResponse(code); - res.Headers["Connection"] = "close"; - - return res; - } - - #endregion - - #region Public Methods - - public void SetCookies(CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var headers = Headers; - var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); - - foreach (var cookie in sorted) - headers.Add("Set-Cookie", cookie.ToString()); - } - - public override string ToString() - { - var output = new StringBuilder(64); - output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); - - var headers = Headers; - foreach (var key in headers.Keys) - output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append(CrLf); - - return output.ToString(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Mask.cs b/SocketHttpListener/Mask.cs deleted file mode 100644 index c56f5b08c0..0000000000 --- a/SocketHttpListener/Mask.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Mask : byte - { - Unmask = 0x0, - Mask = 0x1 - } -} diff --git a/SocketHttpListener/MessageEventArgs.cs b/SocketHttpListener/MessageEventArgs.cs deleted file mode 100644 index 8e2151cb7a..0000000000 --- a/SocketHttpListener/MessageEventArgs.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Text; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the receives - /// a text or binary data frame. - /// If you want to get the received data, you access the or - /// property. - /// - public class MessageEventArgs : EventArgs - { - #region Private Fields - - private string _data; - private Opcode _opcode; - private byte[] _rawData; - - #endregion - - #region Internal Constructors - - internal MessageEventArgs(Opcode opcode, byte[] data) - { - _opcode = opcode; - _rawData = data; - _data = convertToString(opcode, data); - } - - internal MessageEventArgs(Opcode opcode, PayloadData payload) - { - _opcode = opcode; - _rawData = payload.ApplicationData; - _data = convertToString(opcode, _rawData); - } - - #endregion - - #region Public Properties - - /// - /// Gets the received data as a . - /// - /// - /// A that contains the received data. - /// - public string Data => _data; - - /// - /// Gets the received data as an array of . - /// - /// - /// An array of that contains the received data. - /// - public byte[] RawData => _rawData; - - /// - /// Gets the type of the received data. - /// - /// - /// One of the values, indicates the type of the received data. - /// - public Opcode Type => _opcode; - - #endregion - - #region Private Methods - - private static string convertToString(Opcode opcode, byte[] data) - { - return data.Length == 0 - ? string.Empty - : opcode == Opcode.Text - ? Encoding.UTF8.GetString(data, 0, data.Length) - : opcode.ToString(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Net/CookieHelper.cs b/SocketHttpListener/Net/CookieHelper.cs deleted file mode 100644 index 3ad76ff238..0000000000 --- a/SocketHttpListener/Net/CookieHelper.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - public static class CookieHelper - { - internal static CookieCollection Parse(string value, bool response) - { - return response - ? parseResponse(value) - : null; - } - - private static string[] splitCookieHeaderValue(string value) - { - return new List(value.SplitHeaderValue(',', ';')).ToArray(); - } - - private static CookieCollection parseResponse(string value) - { - var cookies = new CookieCollection(); - - Cookie cookie = null; - var pairs = splitCookieHeaderValue(value); - for (int i = 0; i < pairs.Length; i++) - { - var pair = pairs[i].Trim(); - if (pair.Length == 0) - continue; - - if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Version = int.Parse(pair.GetValueInternal("=").Trim('"')); - } - else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase)) - { - var buffer = new StringBuilder(pair.GetValueInternal("="), 32); - if (i < pairs.Length - 1) - buffer.AppendFormat(", {0}", pairs[++i].Trim()); - - if (!DateTime.TryParseExact( - buffer.ToString(), - new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, - new CultureInfo("en-US"), - DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, - out var expires)) - expires = DateTime.Now; - - if (cookie != null && cookie.Expires == DateTime.MinValue) - cookie.Expires = expires.ToLocalTime(); - } - else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase)) - { - var max = int.Parse(pair.GetValueInternal("=").Trim('"')); - var expires = DateTime.Now.AddSeconds((double)max); - if (cookie != null) - cookie.Expires = expires; - } - else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Path = pair.GetValueInternal("="); - } - else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Domain = pair.GetValueInternal("="); - } - else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase)) - { - var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase) - ? "\"\"" - : pair.GetValueInternal("="); - - if (cookie != null) - cookie.Port = port; - } - else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Comment = pair.GetValueInternal("=").UrlDecode(); - } - else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri(); - } - else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Discard = true; - } - else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Secure = true; - } - else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.HttpOnly = true; - } - else - { - if (cookie != null) - cookies.Add(cookie); - - string name; - string val = string.Empty; - - var pos = pair.IndexOf('='); - if (pos == -1) - { - name = pair; - } - else if (pos == pair.Length - 1) - { - name = pair.Substring(0, pos).TrimEnd(' '); - } - else - { - name = pair.Substring(0, pos).TrimEnd(' '); - val = pair.Substring(pos + 1).TrimStart(' '); - } - - cookie = new Cookie(name, val); - } - } - - if (cookie != null) - cookies.Add(cookie); - - return cookies; - } - } -} diff --git a/SocketHttpListener/Opcode.cs b/SocketHttpListener/Opcode.cs deleted file mode 100644 index 69cf3f3728..0000000000 --- a/SocketHttpListener/Opcode.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the opcode that indicates the type of a WebSocket frame. - /// - /// - /// The values of the opcode are defined in - /// Section 5.2 of RFC 6455. - /// - public enum Opcode : byte - { - /// - /// Equivalent to numeric value 0. - /// Indicates a continuation frame. - /// - Cont = 0x0, - /// - /// Equivalent to numeric value 1. - /// Indicates a text frame. - /// - Text = 0x1, - /// - /// Equivalent to numeric value 2. - /// Indicates a binary frame. - /// - Binary = 0x2, - /// - /// Equivalent to numeric value 8. - /// Indicates a connection close frame. - /// - Close = 0x8, - /// - /// Equivalent to numeric value 9. - /// Indicates a ping frame. - /// - Ping = 0x9, - /// - /// Equivalent to numeric value 10. - /// Indicates a pong frame. - /// - Pong = 0xa - } -} diff --git a/SocketHttpListener/PayloadData.cs b/SocketHttpListener/PayloadData.cs deleted file mode 100644 index 6d15a6bcb1..0000000000 --- a/SocketHttpListener/PayloadData.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace SocketHttpListener -{ - internal class PayloadData : IEnumerable - { - #region Private Fields - - private byte[] _applicationData; - private byte[] _extensionData; - private bool _masked; - - #endregion - - #region Public Const Fields - - public const ulong MaxLength = long.MaxValue; - - #endregion - - #region Public Constructors - - public PayloadData() - : this(new byte[0], new byte[0], false) - { - } - - public PayloadData(byte[] applicationData) - : this(new byte[0], applicationData, false) - { - } - - public PayloadData(string applicationData) - : this(new byte[0], Encoding.UTF8.GetBytes(applicationData), false) - { - } - - public PayloadData(byte[] applicationData, bool masked) - : this(new byte[0], applicationData, masked) - { - } - - public PayloadData(byte[] extensionData, byte[] applicationData, bool masked) - { - _extensionData = extensionData; - _applicationData = applicationData; - _masked = masked; - } - - #endregion - - #region Internal Properties - - internal bool ContainsReservedCloseStatusCode => - _applicationData.Length > 1 && - _applicationData.SubArray(0, 2).ToUInt16(ByteOrder.Big).IsReserved(); - - #endregion - - #region Public Properties - - public byte[] ApplicationData => _applicationData; - - public byte[] ExtensionData => _extensionData; - - public bool IsMasked => _masked; - - public ulong Length => (ulong)(_extensionData.Length + _applicationData.Length); - - #endregion - - #region Private Methods - - private static void mask(byte[] src, byte[] key) - { - for (long i = 0; i < src.Length; i++) - src[i] = (byte)(src[i] ^ key[i % 4]); - } - - #endregion - - #region Public Methods - - public IEnumerator GetEnumerator() - { - foreach (byte b in _extensionData) - yield return b; - - foreach (byte b in _applicationData) - yield return b; - } - - public void Mask(byte[] maskingKey) - { - if (_extensionData.Length > 0) - mask(_extensionData, maskingKey); - - if (_applicationData.Length > 0) - mask(_applicationData, maskingKey); - - _masked = !_masked; - } - - public byte[] ToByteArray() - { - return _extensionData.Length > 0 - ? new List(this).ToArray() - : _applicationData; - } - - public override string ToString() - { - return BitConverter.ToString(ToByteArray()); - } - - #endregion - - #region Explicitly Implemented Interface Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Properties/AssemblyInfo.cs b/SocketHttpListener/Properties/AssemblyInfo.cs deleted file mode 100644 index a69bd176f7..0000000000 --- a/SocketHttpListener/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SocketHttpListener")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/SocketHttpListener/Rsv.cs b/SocketHttpListener/Rsv.cs deleted file mode 100644 index 87283791ec..0000000000 --- a/SocketHttpListener/Rsv.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Rsv : byte - { - Off = 0x0, - On = 0x1 - } -} diff --git a/SocketHttpListener/SocketHttpListener.csproj b/SocketHttpListener/SocketHttpListener.csproj deleted file mode 100644 index e700540a91..0000000000 --- a/SocketHttpListener/SocketHttpListener.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - netstandard2.0 - true - false - - - diff --git a/SocketHttpListener/SocketStream.cs b/SocketHttpListener/SocketStream.cs deleted file mode 100644 index f51fde97ef..0000000000 --- a/SocketHttpListener/SocketStream.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; -using System.Net.Sockets; - -namespace SocketHttpListener -{ - public class SocketStream : Stream - { - private readonly Socket _socket; - - public SocketStream(Socket socket, bool ownsSocket) - { - _socket = socket; - } - - public override void Flush() - { - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _socket.Send(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - _socket.EndSend(asyncResult); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _socket.Receive(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return _socket.EndReceive(asyncResult); - } - } -} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs deleted file mode 100644 index afce871fd6..0000000000 --- a/SocketHttpListener/WebSocket.cs +++ /dev/null @@ -1,777 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener -{ - /// - /// Implements the WebSocket interface. - /// - /// - /// The WebSocket class provides a set of methods and properties for two-way communication using - /// the WebSocket protocol (RFC 6455). - /// - public class WebSocket : IDisposable - { - #region Private Fields - - private Action _closeContext; - private CompressionMethod _compression; - private WebSocketContext _context; - private CookieCollection _cookies; - private AutoResetEvent _exitReceiving; - private object _forConn; - private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1); - private object _forMessageEventQueue; - private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1); - private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - private Queue _messageEventQueue; - private string _protocol; - private volatile WebSocketState _readyState; - private AutoResetEvent _receivePong; - private bool _secure; - private Stream _stream; - private const string _version = "13"; - - #endregion - - #region Internal Fields - - internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14. - - #endregion - - #region Internal Constructors - - // As server - internal WebSocket(string protocol) - { - _protocol = protocol; - } - - public void SetContext(HttpListenerWebSocketContext context, Action closeContextFn, Stream stream) - { - _context = context; - - _closeContext = closeContextFn; - _secure = context.IsSecureConnection; - _stream = stream; - - init(); - } - - // In the .NET Framework, this pulls the value from a P/Invoke. Here we just hardcode it to a reasonable default. - public static TimeSpan DefaultKeepAliveInterval => TimeSpan.FromSeconds(30); - - #endregion - - /// - /// Gets the state of the WebSocket connection. - /// - /// - /// One of the enum values, indicates the state of the WebSocket - /// connection. The default value is . - /// - public WebSocketState ReadyState => _readyState; - - #region Public Events - - /// - /// Occurs when the WebSocket connection has been closed. - /// - public event EventHandler OnClose; - - /// - /// Occurs when the gets an error. - /// - public event EventHandler OnError; - - /// - /// Occurs when the receives a message. - /// - public event EventHandler OnMessage; - - /// - /// Occurs when the WebSocket connection has been established. - /// - public event EventHandler OnOpen; - - #endregion - - #region Private Methods - - private async Task CloseAsync(CloseStatusCode code, string reason, bool wait) - { - await CloseAsync(new PayloadData( - await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)), - !code.IsReserved(), - wait).ConfigureAwait(false); - } - - private async Task CloseAsync(PayloadData payload, bool send, bool wait) - { - lock (_forConn) - { - if (_readyState == WebSocketState.CloseSent || _readyState == WebSocketState.Closed) - { - return; - } - - _readyState = WebSocketState.CloseSent; - } - - var e = new CloseEventArgs(payload) - { - WasClean = await CloseHandshakeAsync( - send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null, - wait ? 1000 : 0).ConfigureAwait(false) - }; - - _readyState = WebSocketState.Closed; - try - { - OnClose.Emit(this, e); - } - catch (Exception ex) - { - error("An exception has occurred while OnClose.", ex); - } - } - - private async Task CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout) - { - var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false); - var received = - millisecondsTimeout == 0 || - (sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout)); - - closeServerResources(); - - if (_receivePong != null) - { - _receivePong.Dispose(); - _receivePong = null; - } - - if (_exitReceiving != null) - { - _exitReceiving.Dispose(); - _exitReceiving = null; - } - - var result = sent && received; - - return result; - } - - // As server - private void closeServerResources() - { - if (_closeContext == null) - return; - - try - { - _closeContext(); - } - catch (SocketException) - { - // it could be unable to send the handshake response - } - - _closeContext = null; - _stream = null; - _context = null; - } - - private async Task ConcatenateFragmentsIntoAsync(Stream dest) - { - while (true) - { - var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false); - if (frame.IsFinal) - { - /* FINAL */ - - // CONT - if (frame.IsContinuation) - { - dest.WriteBytes(frame.PayloadData.ApplicationData); - break; - } - - // PING - if (frame.IsPing) - { - processPingFrame(frame); - continue; - } - - // PONG - if (frame.IsPong) - { - processPongFrame(frame); - continue; - } - - // CLOSE - if (frame.IsClose) - return await ProcessCloseFrameAsync(frame).ConfigureAwait(false); - } - else - { - /* MORE */ - - // CONT - if (frame.IsContinuation) - { - dest.WriteBytes(frame.PayloadData.ApplicationData); - continue; - } - } - - // ? - return await ProcessUnsupportedFrameAsync( - frame, - CloseStatusCode.IncorrectData, - "An incorrect data has been received while receiving fragmented data.").ConfigureAwait(false); - } - - return true; - } - - // As server - private HttpResponse createHandshakeCloseResponse(HttpStatusCode code) - { - var res = HttpResponse.CreateCloseResponse(code); - res.Headers["Sec-WebSocket-Version"] = _version; - - return res; - } - - private MessageEventArgs dequeueFromMessageEventQueue() - { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0 - ? _messageEventQueue.Dequeue() - : null; - } - - private void enqueueToMessageEventQueue(MessageEventArgs e) - { - lock (_forMessageEventQueue) - _messageEventQueue.Enqueue(e); - } - - private void error(string message, Exception exception) - { - try - { - if (exception != null) - { - message += ". Exception.Message: " + exception.Message; - } - OnError.Emit(this, new ErrorEventArgs(message)); - } - catch (Exception) - { - } - } - - private void error(string message) - { - try - { - OnError.Emit(this, new ErrorEventArgs(message)); - } - catch (Exception) - { - } - } - - private void init() - { - _compression = CompressionMethod.None; - _cookies = new CookieCollection(); - _forConn = new object(); - _messageEventQueue = new Queue(); - _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; - } - - private async Task OpenAsync() - { - try - { - startReceiving(); - - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while opening.").ConfigureAwait(false); - } - - await _forEvent.WaitAsync().ConfigureAwait(false); - try - { - OnOpen?.Invoke(this, EventArgs.Empty); - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while OnOpen.").ConfigureAwait(false); - } - finally - { - _forEvent.Release(); - } - } - - private async Task ProcessCloseFrameAsync(WebSocketFrame frame) - { - var payload = frame.PayloadData; - await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false); - - return false; - } - - private bool processDataFrame(WebSocketFrame frame) - { - var e = frame.IsCompressed - ? new MessageEventArgs( - frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) - : new MessageEventArgs(frame.Opcode, frame.PayloadData); - - enqueueToMessageEventQueue(e); - return true; - } - - private async Task ProcessExceptionAsync(Exception exception, string message) - { - var code = CloseStatusCode.Abnormal; - var reason = message; - if (exception is WebSocketException) - { - var wsex = (WebSocketException)exception; - code = wsex.Code; - reason = wsex.Message; - } - - error(message ?? code.GetMessage(), exception); - if (_readyState == WebSocketState.Connecting) - { - await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false); - } - else - { - await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false); - } - } - - private Task ProcessFragmentedFrameAsync(WebSocketFrame frame) - { - return frame.IsContinuation // Not first fragment - ? Task.FromResult(true) - : ProcessFragmentsAsync(frame); - } - - private async Task ProcessFragmentsAsync(WebSocketFrame first) - { - using (var buff = new MemoryStream()) - { - buff.WriteBytes(first.PayloadData.ApplicationData); - if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false)) - { - return false; - } - - byte[] data; - if (_compression != CompressionMethod.None) - { - data = buff.DecompressToArray(_compression); - } - else - { - data = buff.ToArray(); - } - - enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data)); - return true; - } - } - - private bool processPingFrame(WebSocketFrame frame) - { - return true; - } - - private bool processPongFrame(WebSocketFrame frame) - { - _receivePong.Set(); - - return true; - } - - private async Task ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason) - { - await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false); - - return false; - } - - private Task ProcessWebSocketFrameAsync(WebSocketFrame frame) - { - // TODO: @bond change to if/else chain - return frame.IsCompressed && _compression == CompressionMethod.None - ? ProcessUnsupportedFrameAsync( - frame, - CloseStatusCode.IncorrectData, - "A compressed data has been received without available decompression method.") - : frame.IsFragmented - ? ProcessFragmentedFrameAsync(frame) - : frame.IsData - ? Task.FromResult(processDataFrame(frame)) - : frame.IsPing - ? Task.FromResult(processPingFrame(frame)) - : frame.IsPong - ? Task.FromResult(processPongFrame(frame)) - : frame.IsClose - ? ProcessCloseFrameAsync(frame) - : ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null); - } - - private async Task SendAsync(Opcode opcode, Stream stream) - { - await _forSend.WaitAsync().ConfigureAwait(false); - try - { - var src = stream; - var compressed = false; - var sent = false; - try - { - if (_compression != CompressionMethod.None) - { - stream = stream.Compress(_compression); - compressed = true; - } - - sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false); - if (!sent) - error("Sending a data has been interrupted."); - } - catch (Exception ex) - { - error("An exception has occurred while sending a data.", ex); - } - finally - { - if (compressed) - stream.Dispose(); - - src.Dispose(); - } - - return sent; - } - finally - { - _forSend.Release(); - } - } - - private async Task SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed) - { - var len = stream.Length; - - /* Not fragmented */ - - if (len == 0) - return await SendAsync(Fin.Final, opcode, mask, new byte[0], compressed).ConfigureAwait(false); - - var quo = len / FragmentLength; - var rem = (int)(len % FragmentLength); - - byte[] buff = null; - if (quo == 0) - { - buff = new byte[rem]; - return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && - await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); - } - - buff = new byte[FragmentLength]; - if (quo == 1 && rem == 0) - return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength && - await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); - - /* Send fragmented */ - - // Begin - if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || - !await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false)) - return false; - - var n = rem == 0 ? quo - 2 : quo - 1; - for (long i = 0; i < n; i++) - if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || - !await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false)) - return false; - - // End - if (rem == 0) - rem = FragmentLength; - else - buff = new byte[rem]; - - return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && - await SendAsync(Fin.Final, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false); - } - - private Task SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) - { - lock (_forConn) - { - if (_readyState != WebSocketState.Open) - { - return Task.FromResult(false); - } - - return WriteBytesAsync( - WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray()); - } - } - - // As server - private Task SendHttpResponseAsync(HttpResponse response) - => WriteBytesAsync(response.ToByteArray()); - - private void startReceiving() - { - if (_messageEventQueue.Count > 0) - { - _messageEventQueue.Clear(); - } - - _exitReceiving = new AutoResetEvent(false); - _receivePong = new AutoResetEvent(false); - - Action receive = null; - receive = async () => await WebSocketFrame.ReadAsync( - _stream, - true, - async frame => - { - if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed) - { - receive(); - - if (!frame.IsData) - { - return; - } - - await _forEvent.WaitAsync().ConfigureAwait(false); - - try - { - var e = dequeueFromMessageEventQueue(); - if (e != null && _readyState == WebSocketState.Open) - { - OnMessage.Emit(this, e); - } - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false); - } - finally - { - _forEvent.Release(); - } - - } - else if (_exitReceiving != null) - { - _exitReceiving.Set(); - } - }, - async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false); - - receive(); - } - - private async Task WriteBytesAsync(byte[] data) - { - try - { - await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); - return true; - } - catch (Exception) - { - return false; - } - } - - #endregion - - #region Internal Methods - - // As server - internal async Task CloseAsync(HttpResponse response) - { - _readyState = WebSocketState.CloseSent; - await SendHttpResponseAsync(response).ConfigureAwait(false); - - closeServerResources(); - - _readyState = WebSocketState.Closed; - } - - // As server - internal Task CloseAsync(HttpStatusCode code) - => CloseAsync(createHandshakeCloseResponse(code)); - - // As server - public async Task ConnectAsServer() - { - try - { - _readyState = WebSocketState.Open; - await OpenAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while connecting.").ConfigureAwait(false); - } - } - - #endregion - - #region Public Methods - - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - public Task CloseAsync() - { - var msg = _readyState.CheckIfClosable(); - if (msg != null) - { - error(msg); - - return Task.CompletedTask; - } - - var send = _readyState == WebSocketState.Open; - return CloseAsync(new PayloadData(), send, send); - } - - /// - /// Closes the WebSocket connection with the specified - /// and , and releases all associated resources. - /// - /// - /// This method emits a event if the size - /// of is greater than 123 bytes. - /// - /// - /// One of the enum values, represents the status code - /// indicating the reason for the close. - /// - /// - /// A that represents the reason for the close. - /// - public async Task CloseAsync(CloseStatusCode code, string reason) - { - byte[] data = null; - var msg = _readyState.CheckIfClosable() ?? - (data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason"); - - if (msg != null) - { - error(msg); - - return; - } - - var send = _readyState == WebSocketState.Open && !code.IsReserved(); - await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false); - } - - /// - /// Sends a binary asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - public Task SendAsync(byte[] data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var msg = _readyState.CheckIfOpen(); - if (msg != null) - { - throw new Exception(msg); - } - - return SendAsync(Opcode.Binary, new MemoryStream(data)); - } - - /// - /// Sends a text asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - public Task SendAsync(string data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var msg = _readyState.CheckIfOpen(); - if (msg != null) - { - throw new Exception(msg); - } - - return SendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data))); - } - - #endregion - - #region Explicit Interface Implementation - - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - /// - /// This method closes the WebSocket connection with . - /// - void IDisposable.Dispose() - { - CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult(); - } - - #endregion - } -} diff --git a/SocketHttpListener/WebSocketException.cs b/SocketHttpListener/WebSocketException.cs deleted file mode 100644 index e86c46d0f5..0000000000 --- a/SocketHttpListener/WebSocketException.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; - -namespace SocketHttpListener -{ - /// - /// The exception that is thrown when a gets a fatal error. - /// - public class WebSocketException : Exception - { - #region Internal Constructors - - internal WebSocketException() - : this(CloseStatusCode.Abnormal, null, null) - { - } - - internal WebSocketException(string message) - : this(CloseStatusCode.Abnormal, message, null) - { - } - - internal WebSocketException(CloseStatusCode code) - : this(code, null, null) - { - } - - internal WebSocketException(string message, Exception innerException) - : this(CloseStatusCode.Abnormal, message, innerException) - { - } - - internal WebSocketException(CloseStatusCode code, string message) - : this(code, message, null) - { - } - - internal WebSocketException(CloseStatusCode code, string message, Exception innerException) - : base(message ?? code.GetMessage(), innerException) - { - Code = code; - } - - #endregion - - #region Public Properties - - /// - /// Gets the status code indicating the cause for the exception. - /// - /// - /// One of the enum values, represents the status code indicating - /// the cause for the exception. - /// - public CloseStatusCode Code - { - get; private set; - } - - #endregion - } -} diff --git a/SocketHttpListener/WebSocketFrame.cs b/SocketHttpListener/WebSocketFrame.cs deleted file mode 100644 index 8ec64026bd..0000000000 --- a/SocketHttpListener/WebSocketFrame.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace SocketHttpListener -{ - internal class WebSocketFrame : IEnumerable - { - #region Private Fields - - private byte[] _extPayloadLength; - private Fin _fin; - private Mask _mask; - private byte[] _maskingKey; - private Opcode _opcode; - private PayloadData _payloadData; - private byte _payloadLength; - private Rsv _rsv1; - private Rsv _rsv2; - private Rsv _rsv3; - - #endregion - - #region Internal Fields - - internal static readonly byte[] EmptyUnmaskPingData; - - #endregion - - #region Static Constructor - - static WebSocketFrame() - { - EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray(); - } - - #endregion - - #region Private Constructors - - private WebSocketFrame() - { - } - - #endregion - - #region Internal Constructors - - internal WebSocketFrame(Opcode opcode, PayloadData payload) - : this(Fin.Final, opcode, Mask.Mask, payload, false) - { - } - - internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload) - : this(Fin.Final, opcode, mask, payload, false) - { - } - - internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload) - : this(fin, opcode, mask, payload, false) - { - } - - internal WebSocketFrame( - Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed) - { - _fin = fin; - _rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off; - _rsv2 = Rsv.Off; - _rsv3 = Rsv.Off; - _opcode = opcode; - _mask = mask; - - var len = payload.Length; - if (len < 126) - { - _payloadLength = (byte)len; - _extPayloadLength = new byte[0]; - } - else if (len < 0x010000) - { - _payloadLength = (byte)126; - _extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big); - } - else - { - _payloadLength = (byte)127; - _extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big); - } - - if (mask == Mask.Mask) - { - _maskingKey = createMaskingKey(); - payload.Mask(_maskingKey); - } - else - { - _maskingKey = new byte[0]; - } - - _payloadData = payload; - } - - #endregion - - #region Public Properties - - public byte[] ExtendedPayloadLength => _extPayloadLength; - - public Fin Fin => _fin; - - public bool IsBinary => _opcode == Opcode.Binary; - - public bool IsClose => _opcode == Opcode.Close; - - public bool IsCompressed => _rsv1 == Rsv.On; - - public bool IsContinuation => _opcode == Opcode.Cont; - - public bool IsControl => _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong; - - public bool IsData => _opcode == Opcode.Binary || _opcode == Opcode.Text; - - public bool IsFinal => _fin == Fin.Final; - - public bool IsFragmented => _fin == Fin.More || _opcode == Opcode.Cont; - - public bool IsMasked => _mask == Mask.Mask; - - public bool IsPerMessageCompressed => (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On; - - public bool IsPing => _opcode == Opcode.Ping; - - public bool IsPong => _opcode == Opcode.Pong; - - public bool IsText => _opcode == Opcode.Text; - - public ulong Length => 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length; - - public Mask Mask => _mask; - - public byte[] MaskingKey => _maskingKey; - - public Opcode Opcode => _opcode; - - public PayloadData PayloadData => _payloadData; - - public byte PayloadLength => _payloadLength; - - public Rsv Rsv1 => _rsv1; - - public Rsv Rsv2 => _rsv2; - - public Rsv Rsv3 => _rsv3; - - #endregion - - #region Private Methods - - private byte[] createMaskingKey() - { - var key = new byte[4]; - var rand = new Random(); - rand.NextBytes(key); - - return key; - } - - private static bool isControl(Opcode opcode) - { - return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong; - } - - private static bool isData(Opcode opcode) - { - return opcode == Opcode.Text || opcode == Opcode.Binary; - } - - private static async Task ReadAsync(byte[] header, Stream stream, bool unmask) - { - /* Header */ - - // FIN - var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More; - // RSV1 - var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off; - // RSV2 - var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off; - // RSV3 - var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off; - // Opcode - var opcode = (Opcode)(header[0] & 0x0f); - // MASK - var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask; - // Payload Length - var payloadLen = (byte)(header[1] & 0x7f); - - // Check if correct frame. - var incorrect = isControl(opcode) && fin == Fin.More - ? "A control frame is fragmented." - : !isData(opcode) && rsv1 == Rsv.On - ? "A non data frame is compressed." - : null; - - if (incorrect != null) - throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect); - - // Check if consistent frame. - if (isControl(opcode) && payloadLen > 125) - throw new WebSocketException( - CloseStatusCode.InconsistentData, - "The length of payload data of a control frame is greater than 125 bytes."); - - var frame = new WebSocketFrame(); - frame._fin = fin; - frame._rsv1 = rsv1; - frame._rsv2 = rsv2; - frame._rsv3 = rsv3; - frame._opcode = opcode; - frame._mask = mask; - frame._payloadLength = payloadLen; - - /* Extended Payload Length */ - - var size = payloadLen < 126 - ? 0 - : payloadLen == 126 - ? 2 - : 8; - - var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty(); - if (size > 0 && extPayloadLen.Length != size) - throw new WebSocketException( - "The 'Extended Payload Length' of a frame cannot be read from the data source."); - - frame._extPayloadLength = extPayloadLen; - - /* Masking Key */ - - var masked = mask == Mask.Mask; - var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty(); - if (masked && maskingKey.Length != 4) - throw new WebSocketException( - "The 'Masking Key' of a frame cannot be read from the data source."); - - frame._maskingKey = maskingKey; - - /* Payload Data */ - - ulong len = payloadLen < 126 - ? payloadLen - : payloadLen == 126 - ? extPayloadLen.ToUInt16(ByteOrder.Big) - : extPayloadLen.ToUInt64(ByteOrder.Big); - - byte[] data = null; - if (len > 0) - { - // Check if allowable payload data length. - if (payloadLen > 126 && len > PayloadData.MaxLength) - throw new WebSocketException( - CloseStatusCode.TooBig, - "The length of 'Payload Data' of a frame is greater than the allowable length."); - - data = payloadLen > 126 - ? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false) - : await stream.ReadBytesAsync((int)len).ConfigureAwait(false); - - //if (data.LongLength != (long)len) - // throw new WebSocketException( - // "The 'Payload Data' of a frame cannot be read from the data source."); - } - else - { - data = Array.Empty(); - } - - var payload = new PayloadData(data, masked); - if (masked && unmask) - { - payload.Mask(maskingKey); - frame._mask = Mask.Unmask; - frame._maskingKey = Array.Empty(); - } - - frame._payloadData = payload; - return frame; - } - - #endregion - - #region Internal Methods - - internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data) - { - return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data)); - } - - internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload) - { - return new WebSocketFrame(Opcode.Close, mask, payload); - } - - internal static async Task CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason) - { - return new WebSocketFrame( - Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false))); - } - - internal static WebSocketFrame CreatePingFrame(Mask mask) - { - return new WebSocketFrame(Opcode.Ping, mask, new PayloadData()); - } - - internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data) - { - return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data)); - } - - internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload) - { - return new WebSocketFrame(Opcode.Pong, mask, payload); - } - - internal static WebSocketFrame CreateWebSocketFrame( - Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) - { - return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed); - } - - internal static Task ReadAsync(Stream stream) - => ReadAsync(stream, true); - - internal static async Task ReadAsync(Stream stream, bool unmask) - { - var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); - if (header.Length != 2) - { - throw new WebSocketException( - "The header part of a frame cannot be read from the data source."); - } - - return await ReadAsync(header, stream, unmask).ConfigureAwait(false); - } - - internal static async Task ReadAsync( - Stream stream, bool unmask, Action completed, Action error) - { - try - { - var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); - if (header.Length != 2) - { - throw new WebSocketException( - "The header part of a frame cannot be read from the data source."); - } - - var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false); - completed?.Invoke(frame); - } - catch (Exception ex) - { - error.Invoke(ex); - } - } - - #endregion - - #region Public Methods - - public IEnumerator GetEnumerator() - { - foreach (var b in ToByteArray()) - yield return b; - } - - public void Print(bool dumped) - { - //Console.WriteLine(dumped ? dump(this) : print(this)); - } - - public byte[] ToByteArray() - { - using (var buff = new MemoryStream()) - { - var header = (int)_fin; - header = (header << 1) + (int)_rsv1; - header = (header << 1) + (int)_rsv2; - header = (header << 1) + (int)_rsv3; - header = (header << 4) + (int)_opcode; - header = (header << 1) + (int)_mask; - header = (header << 7) + (int)_payloadLength; - buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2); - - if (_payloadLength > 125) - buff.Write(_extPayloadLength, 0, _extPayloadLength.Length); - - if (_mask == Mask.Mask) - buff.Write(_maskingKey, 0, _maskingKey.Length); - - if (_payloadLength > 0) - { - var payload = _payloadData.ToByteArray(); - if (_payloadLength < 127) - buff.Write(payload, 0, payload.Length); - else - buff.WriteBytes(payload); - } - - return buff.ToArray(); - } - } - - public override string ToString() - { - return BitConverter.ToString(ToByteArray()); - } - - #endregion - - #region Explicitly Implemented Interface Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} From 25d3d0b731db793e4afc39ff40e0b6a1a27d6ad8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 08:02:32 +0100 Subject: [PATCH 21/59] Remove some unused stuff --- .../SocketSharp/RequestMono.cs | 17 ----------------- .../SocketSharp/WebSocketSharpRequest.cs | 17 ----------------- MediaBrowser.Model/Services/IRequest.cs | 10 ---------- 3 files changed, 44 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index a8142aef68..113f76b10b 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -105,14 +105,6 @@ namespace Emby.Server.Implementations.SocketSharp 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; @@ -129,8 +121,6 @@ namespace Emby.Server.Implementations.SocketSharp 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) @@ -210,13 +200,6 @@ namespace Emby.Server.Implementations.SocketSharp 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) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 53dce667ba..bddccf68ba 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -48,8 +48,6 @@ namespace Emby.Server.Implementations.SocketSharp public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - public string UserHostAddress => request.HttpContext.Connection.RemoteIpAddress.ToString(); - public string XForwardedFor => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); @@ -142,19 +140,6 @@ namespace Emby.Server.Implementations.SocketSharp 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)) @@ -171,8 +156,6 @@ namespace Emby.Server.Implementations.SocketSharp return ip; } - public bool IsSecureConnection => request.IsHttps || XForwardedProtocol == "https"; - public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); private Dictionary items; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index c7dccdb6fd..0fd4ea37b1 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -61,11 +61,6 @@ namespace MediaBrowser.Model.Services string AbsoluteUri { get; } - /// - /// The Remote Ip as reported by Request.UserHostAddress - /// - string UserHostAddress { get; } - /// /// The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress /// @@ -76,11 +71,6 @@ namespace MediaBrowser.Model.Services /// string Authorization { get; } - /// - /// e.g. is https or not - /// - bool IsSecureConnection { get; } - string[] AcceptTypes { get; } string PathInfo { get; } From 333bd2107a05c656b8fe27364dcd05fc7ca249d5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 12:40:18 +0100 Subject: [PATCH 22/59] Remove HttpUtility --- Emby.Dlna/PlayTo/PlayToController.cs | 22 +- .../HttpServer/HttpListenerHost.cs | 3 +- MediaBrowser.Model/Services/HttpUtility.cs | 691 ------------------ 3 files changed, 14 insertions(+), 702 deletions(-) delete mode 100644 MediaBrowser.Model/Services/HttpUtility.cs diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index be86dde16a..be6810d0e9 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -19,7 +19,9 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna.PlayTo { @@ -847,13 +849,13 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - QueryParamCollection values = MyHttpUtility.ParseQueryString(query); + var values = QueryHelpers.ParseQuery(query); - request.DeviceProfileId = values.Get("DeviceProfileId"); - request.DeviceId = values.Get("DeviceId"); - request.MediaSourceId = values.Get("MediaSourceId"); - request.LiveStreamId = values.Get("LiveStreamId"); - request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase); + request.DeviceProfileId = values["DeviceProfileId"].ToString(); + request.DeviceId = values["DeviceId"].ToString(); + request.MediaSourceId = values["MediaSourceId"].ToString(); + request.LiveStreamId = values["LiveStreamId"].ToString(); + request.IsDirectStream = string.Equals("true", values["Static"].ToString(), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); @@ -867,9 +869,9 @@ namespace Emby.Dlna.PlayTo } } - private static int? GetIntValue(QueryParamCollection values, string name) + private static int? GetIntValue(IReadOnlyDictionary values, string name) { - var value = values.Get(name); + var value = values[name].ToString(); if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { @@ -879,9 +881,9 @@ namespace Emby.Dlna.PlayTo return null; } - private static long GetLongValue(QueryParamCollection values, string name) + private static long GetLongValue(IReadOnlyDictionary values, string name) { - var value = values.Get(name); + var value = values[name].ToString(); if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 5b1cb433a9..2abc6c2f4f 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -298,7 +299,7 @@ namespace Emby.Server.Implementations.HttpServer var uri = new Uri(url); // this gets all the query string key value pairs as a collection - var newQueryString = MyHttpUtility.ParseQueryString(uri.Query); + var newQueryString = QueryHelpers.ParseQuery(uri.Query); var originalCount = newQueryString.Count; diff --git a/MediaBrowser.Model/Services/HttpUtility.cs b/MediaBrowser.Model/Services/HttpUtility.cs deleted file mode 100644 index be180334c6..0000000000 --- a/MediaBrowser.Model/Services/HttpUtility.cs +++ /dev/null @@ -1,691 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace MediaBrowser.Model.Services -{ - public static class MyHttpUtility - { - // Must be sorted - static readonly long[] entities = new long[] { - (long)'A' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'A' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'A' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'B' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'C' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'C' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'D' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'D' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'E' << 56 | (long)'T' << 48 | (long)'H' << 40, - (long)'E' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'E' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'E' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'E' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'G' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'I' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'I' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'I' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'K' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'L' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'M' << 56 | (long)'u' << 48, - (long)'N' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'N' << 56 | (long)'u' << 48, - (long)'O' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'O' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'O' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'O' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'O' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'O' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'P' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'P' << 56 | (long)'i' << 48, - (long)'P' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'P' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'R' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'S' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'S' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'T' << 56 | (long)'H' << 48 | (long)'O' << 40 | (long)'R' << 32 | (long)'N' << 24, - (long)'T' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'T' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'U' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'U' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'U' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'X' << 56 | (long)'i' << 48, - (long)'Y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'Y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'Z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'a' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'a' << 56 | (long)'c' << 48 | (long)'u' << 40 | (long)'t' << 32 | (long)'e' << 24, - (long)'a' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'l' << 48 | (long)'e' << 40 | (long)'f' << 32 | (long)'s' << 24 | (long)'y' << 16 | (long)'m' << 8, - (long)'a' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'a' << 56 | (long)'m' << 48 | (long)'p' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'d' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'g' << 40, - (long)'a' << 56 | (long)'p' << 48 | (long)'o' << 40 | (long)'s' << 32, - (long)'a' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'s' << 48 | (long)'y' << 40 | (long)'m' << 32 | (long)'p' << 24, - (long)'a' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'b' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'b' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'b' << 56 | (long)'r' << 48 | (long)'v' << 40 | (long)'b' << 32 | (long)'a' << 24 | (long)'r' << 16, - (long)'b' << 56 | (long)'u' << 48 | (long)'l' << 40 | (long)'l' << 32, - (long)'c' << 56 | (long)'a' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'c' << 56 | (long)'e' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'c' << 56 | (long)'e' << 48 | (long)'n' << 40 | (long)'t' << 32, - (long)'c' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'c' << 56 | (long)'i' << 48 | (long)'r' << 40 | (long)'c' << 32, - (long)'c' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'b' << 32 | (long)'s' << 24, - (long)'c' << 56 | (long)'o' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'c' << 56 | (long)'o' << 48 | (long)'p' << 40 | (long)'y' << 32, - (long)'c' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'r' << 24, - (long)'c' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'n' << 16, - (long)'d' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'d' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'d' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'a' << 40 | (long)'m' << 32 | (long)'s' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'v' << 40 | (long)'i' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'e' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'m' << 48 | (long)'p' << 40 | (long)'t' << 32 | (long)'y' << 24, - (long)'e' << 56 | (long)'m' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'n' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'e' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'i' << 32 | (long)'v' << 24, - (long)'e' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'e' << 56 | (long)'t' << 48 | (long)'h' << 40, - (long)'e' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'e' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'o' << 32, - (long)'e' << 56 | (long)'x' << 48 | (long)'i' << 40 | (long)'s' << 32 | (long)'t' << 24, - (long)'f' << 56 | (long)'n' << 48 | (long)'o' << 40 | (long)'f' << 32, - (long)'f' << 56 | (long)'o' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'l' << 24 | (long)'l' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'2' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'3' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'l' << 24, - (long)'g' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'g' << 56 | (long)'e' << 48, - (long)'g' << 56 | (long)'t' << 48, - (long)'h' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'t' << 24 | (long)'s' << 16, - (long)'h' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'l' << 32 | (long)'i' << 24 | (long)'p' << 16, - (long)'i' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'i' << 56 | (long)'e' << 48 | (long)'x' << 40 | (long)'c' << 32 | (long)'l' << 24, - (long)'i' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'m' << 48 | (long)'a' << 40 | (long)'g' << 32 | (long)'e' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'f' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'t' << 40, - (long)'i' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'i' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'e' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'i' << 56 | (long)'s' << 48 | (long)'i' << 40 | (long)'n' << 32, - (long)'i' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'k' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'l' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'l' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'l' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'e' << 48, - (long)'l' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'w' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'z' << 40, - (long)'l' << 56 | (long)'r' << 48 | (long)'m' << 40, - (long)'l' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'l' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'t' << 48, - (long)'m' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'r' << 32, - (long)'m' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'c' << 40 | (long)'r' << 32 | (long)'o' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'d' << 40 | (long)'d' << 32 | (long)'o' << 24 | (long)'t' << 16, - (long)'m' << 56 | (long)'i' << 48 | (long)'n' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'m' << 56 | (long)'u' << 48, - (long)'n' << 56 | (long)'a' << 48 | (long)'b' << 40 | (long)'l' << 32 | (long)'a' << 24, - (long)'n' << 56 | (long)'b' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'n' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'n' << 56 | (long)'e' << 48, - (long)'n' << 56 | (long)'i' << 48, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'n' << 56 | (long)'s' << 48 | (long)'u' << 40 | (long)'b' << 32, - (long)'n' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'n' << 56 | (long)'u' << 48, - (long)'o' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'o' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'o' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'l' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'e' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'o' << 56 | (long)'p' << 48 | (long)'l' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'o' << 56 | (long)'r' << 48, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'f' << 32, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'m' << 32, - (long)'o' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'o' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'a' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'t' << 32, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'m' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'p' << 56 | (long)'i' << 48, - (long)'p' << 56 | (long)'i' << 48 | (long)'v' << 40, - (long)'p' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'s' << 32 | (long)'m' << 24 | (long)'n' << 16, - (long)'p' << 56 | (long)'o' << 48 | (long)'u' << 40 | (long)'n' << 32 | (long)'d' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'d' << 32, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'q' << 56 | (long)'u' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'r' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'c' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'r' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'l' << 32, - (long)'r' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'r' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'r' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'r' << 56 | (long)'l' << 48 | (long)'m' << 40, - (long)'r' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'r' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'b' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'s' << 56 | (long)'d' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'e' << 48 | (long)'c' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'h' << 48 | (long)'y' << 40, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24 | (long)'f' << 16, - (long)'s' << 56 | (long)'i' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'p' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'1' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'2' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'3' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'z' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'t' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'4' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24 | (long)'s' << 16 | (long)'y' << 8 | (long)'m' << 0, - (long)'t' << 56 | (long)'h' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'s' << 24 | (long)'p' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'o' << 40 | (long)'r' << 32 | (long)'n' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'l' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'m' << 40 | (long)'e' << 32 | (long)'s' << 24, - (long)'t' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'u' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'u' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'m' << 48 | (long)'l' << 40, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'h' << 24, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'u' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'w' << 56 | (long)'e' << 48 | (long)'i' << 40 | (long)'e' << 32 | (long)'r' << 24 | (long)'p' << 16, - (long)'x' << 56 | (long)'i' << 48, - (long)'y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'y' << 56 | (long)'e' << 48 | (long)'n' << 40, - (long)'y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'z' << 56 | (long)'w' << 48 | (long)'j' << 40, - (long)'z' << 56 | (long)'w' << 48 | (long)'n' << 40 | (long)'j' << 32 - }; - - static readonly char[] entities_values = new char[] { - '\u00C6', '\u00C1', '\u00C2', '\u00C0', '\u0391', '\u00C5', '\u00C3', '\u00C4', '\u0392', '\u00C7', '\u03A7', - '\u2021', '\u0394', '\u00D0', '\u00C9', '\u00CA', '\u00C8', '\u0395', '\u0397', '\u00CB', '\u0393', '\u00CD', - '\u00CE', '\u00CC', '\u0399', '\u00CF', '\u039A', '\u039B', '\u039C', '\u00D1', '\u039D', '\u0152', '\u00D3', - '\u00D4', '\u00D2', '\u03A9', '\u039F', '\u00D8', '\u00D5', '\u00D6', '\u03A6', '\u03A0', '\u2033', '\u03A8', - '\u03A1', '\u0160', '\u03A3', '\u00DE', '\u03A4', '\u0398', '\u00DA', '\u00DB', '\u00D9', '\u03A5', '\u00DC', - '\u039E', '\u00DD', '\u0178', '\u0396', '\u00E1', '\u00E2', '\u00B4', '\u00E6', '\u00E0', '\u2135', '\u03B1', - '\u0026', '\u2227', '\u2220', '\u0027', '\u00E5', '\u2248', '\u00E3', '\u00E4', '\u201E', '\u03B2', '\u00A6', - '\u2022', '\u2229', '\u00E7', '\u00B8', '\u00A2', '\u03C7', '\u02C6', '\u2663', '\u2245', '\u00A9', '\u21B5', - '\u222A', '\u00A4', '\u21D3', '\u2020', '\u2193', '\u00B0', '\u03B4', '\u2666', '\u00F7', '\u00E9', '\u00EA', - '\u00E8', '\u2205', '\u2003', '\u2002', '\u03B5', '\u2261', '\u03B7', '\u00F0', '\u00EB', '\u20AC', '\u2203', - '\u0192', '\u2200', '\u00BD', '\u00BC', '\u00BE', '\u2044', '\u03B3', '\u2265', '\u003E', '\u21D4', '\u2194', - '\u2665', '\u2026', '\u00ED', '\u00EE', '\u00A1', '\u00EC', '\u2111', '\u221E', '\u222B', '\u03B9', '\u00BF', - '\u2208', '\u00EF', '\u03BA', '\u21D0', '\u03BB', '\u2329', '\u00AB', '\u2190', '\u2308', '\u201C', '\u2264', - '\u230A', '\u2217', '\u25CA', '\u200E', '\u2039', '\u2018', '\u003C', '\u00AF', '\u2014', '\u00B5', '\u00B7', - '\u2212', '\u03BC', '\u2207', '\u00A0', '\u2013', '\u2260', '\u220B', '\u00AC', '\u2209', '\u2284', '\u00F1', - '\u03BD', '\u00F3', '\u00F4', '\u0153', '\u00F2', '\u203E', '\u03C9', '\u03BF', '\u2295', '\u2228', '\u00AA', - '\u00BA', '\u00F8', '\u00F5', '\u2297', '\u00F6', '\u00B6', '\u2202', '\u2030', '\u22A5', '\u03C6', '\u03C0', - '\u03D6', '\u00B1', '\u00A3', '\u2032', '\u220F', '\u221D', '\u03C8', '\u0022', '\u21D2', '\u221A', '\u232A', - '\u00BB', '\u2192', '\u2309', '\u201D', '\u211C', '\u00AE', '\u230B', '\u03C1', '\u200F', '\u203A', '\u2019', - '\u201A', '\u0161', '\u22C5', '\u00A7', '\u00AD', '\u03C3', '\u03C2', '\u223C', '\u2660', '\u2282', '\u2286', - '\u2211', '\u2283', '\u00B9', '\u00B2', '\u00B3', '\u2287', '\u00DF', '\u03C4', '\u2234', '\u03B8', '\u03D1', - '\u2009', '\u00FE', '\u02DC', '\u00D7', '\u2122', '\u21D1', '\u00FA', '\u2191', '\u00FB', '\u00F9', '\u00A8', - '\u03D2', '\u03C5', '\u00FC', '\u2118', '\u03BE', '\u00FD', '\u00A5', '\u00FF', '\u03B6', '\u200D', '\u200C' - }; - - #region Methods - - static void WriteCharBytes(IList buf, char ch, Encoding e) - { - if (ch > 255) - { - foreach (byte b in e.GetBytes(new char[] { ch })) - buf.Add(b); - } - else - buf.Add((byte)ch); - } - - public static string UrlDecode(string s, Encoding e) - { - if (null == s) - return null; - - if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1) - return s; - - if (e == null) - e = Encoding.UTF8; - - long len = s.Length; - var bytes = new List(); - int xchar; - char ch; - - for (int i = 0; i < len; i++) - { - ch = s[i]; - if (ch == '%' && i + 2 < len && s[i + 1] != '%') - { - if (s[i + 1] == 'u' && i + 5 < len) - { - // unicode hex sequence - xchar = GetChar(s, i + 2, 4); - if (xchar != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 5; - } - else - WriteCharBytes(bytes, '%', e); - } - else if ((xchar = GetChar(s, i + 1, 2)) != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 2; - } - else - { - WriteCharBytes(bytes, '%', e); - } - continue; - } - - if (ch == '+') - WriteCharBytes(bytes, ' ', e); - else - WriteCharBytes(bytes, ch, e); - } - - byte[] buf = bytes.ToArray(); - bytes = null; - return e.GetString(buf, 0, buf.Length); - - } - - static int GetInt(byte b) - { - char c = (char)b; - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -1; - } - - static int GetChar(string str, int offset, int length) - { - int val = 0; - int end = length + offset; - for (int i = offset; i < end; i++) - { - char c = str[i]; - if (c > 127) - return -1; - - int current = GetInt((byte)c); - if (current == -1) - return -1; - val = (val << 4) + current; - } - - return val; - } - - static bool TryConvertKeyToEntity(string key, out char value) - { - var token = CalculateKeyValue(key); - if (token == 0) - { - value = '\0'; - return false; - } - - var idx = Array.BinarySearch(entities, token); - if (idx < 0) - { - value = '\0'; - return false; - } - - value = entities_values[idx]; - return true; - } - - static long CalculateKeyValue(string s) - { - if (s.Length > 8) - return 0; - - long key = 0; - for (int i = 0; i < s.Length; ++i) - { - long ch = s[i]; - if (ch > 'z' || ch < '0') - return 0; - - key |= ch << ((7 - i) * 8); - } - - return key; - } - - /// - /// Decodes an HTML-encoded string and returns the decoded string. - /// - /// The HTML string to decode. - /// The decoded text. - public static string HtmlDecode(string s) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - - if (s.IndexOf('&') == -1) - return s; - - var entity = new StringBuilder(); - var output = new StringBuilder(); - int len = s.Length; - // 0 -> nothing, - // 1 -> right after '&' - // 2 -> between '&' and ';' but no '#' - // 3 -> '#' found after '&' and getting numbers - int state = 0; - int number = 0; - int digit_start = 0; - bool hex_number = false; - - for (int i = 0; i < len; i++) - { - char c = s[i]; - if (state == 0) - { - if (c == '&') - { - entity.Append(c); - state = 1; - } - else - { - output.Append(c); - } - continue; - } - - if (c == '&') - { - state = 1; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - output.Append(entity.ToString()); - entity.Length = 0; - entity.Append('&'); - continue; - } - - switch (state) - { - case 1: - if (c == ';') - { - state = 0; - output.Append(entity.ToString()); - output.Append(c); - entity.Length = 0; - break; - } - - number = 0; - hex_number = false; - if (c != '#') - { - state = 2; - } - else - { - state = 3; - } - entity.Append(c); - - break; - case 2: - entity.Append(c); - if (c == ';') - { - string key = entity.ToString(); - state = 0; - entity.Length = 0; - - if (key.Length > 1) - { - var skey = key.Substring(1, key.Length - 2); - if (TryConvertKeyToEntity(skey, out c)) - { - output.Append(c); - break; - } - } - - output.Append(key); - } - - break; - case 3: - if (c == ';') - { - if (number < 0x10000) - { - output.Append((char)number); - } - else - { - output.Append((char)(0xd800 + ((number - 0x10000) >> 10))); - output.Append((char)(0xdc00 + ((number - 0x10000) & 0x3ff))); - } - state = 0; - entity.Length = 0; - digit_start = 0; - break; - } - - if (c == 'x' || c == 'X' && !hex_number) - { - digit_start = i; - hex_number = true; - break; - } - - if (char.IsDigit(c)) - { - if (digit_start == 0) - digit_start = i; - - number = number * (hex_number ? 16 : 10) + ((int)c - '0'); - break; - } - - if (hex_number) - { - if (c >= 'a' && c <= 'f') - { - number = number * 16 + 10 + ((int)c - 'a'); - break; - } - if (c >= 'A' && c <= 'F') - { - number = number * 16 + 10 + ((int)c - 'A'); - break; - } - } - - state = 2; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - entity.Append(c); - break; - } - } - - if (entity.Length > 0) - { - output.Append(entity); - } - else if (digit_start > 0) - { - output.Append(s, digit_start, s.Length - digit_start); - } - return output.ToString(); - } - - public static QueryParamCollection ParseQueryString(string query) - { - return ParseQueryString(query, Encoding.UTF8); - } - - public static QueryParamCollection ParseQueryString(string query, Encoding encoding) - { - if (query == null) - throw new ArgumentNullException(nameof(query)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); - if (query.Length == 0 || (query.Length == 1 && query[0] == '?')) - return new QueryParamCollection(); - if (query[0] == '?') - query = query.Substring(1); - - var result = new QueryParamCollection(); - ParseQueryString(query, encoding, result); - return result; - } - - internal static void ParseQueryString(string query, Encoding encoding, QueryParamCollection result) - { - if (query.Length == 0) - return; - - string decoded = HtmlDecode(query); - int decodedLength = decoded.Length; - int namePos = 0; - bool first = true; - while (namePos <= decodedLength) - { - int valuePos = -1, valueEnd = -1; - for (int q = namePos; q < decodedLength; q++) - { - if (valuePos == -1 && decoded[q] == '=') - { - valuePos = q + 1; - } - else if (decoded[q] == '&') - { - valueEnd = q; - break; - } - } - - if (first) - { - first = false; - if (decoded[namePos] == '?') - namePos++; - } - - string name, value; - if (valuePos == -1) - { - name = null; - valuePos = namePos; - } - else - { - name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding); - } - if (valueEnd < 0) - { - namePos = -1; - valueEnd = decoded.Length; - } - else - { - namePos = valueEnd + 1; - } - value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding); - - result.Add(name, value); - if (namePos == -1) - break; - } - } - #endregion // Methods - } -} From 91afaaf8fe43035bb4832da44b6d6741d2815fb5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 12:45:06 +0100 Subject: [PATCH 23/59] Cleanup in QueryParamCollection --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- .../Services/QueryParamCollection.cs | 59 +------------------ 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index be6810d0e9..2da8bb8609 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -849,7 +849,7 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - var values = QueryHelpers.ParseQuery(query); + Dictionary values = QueryHelpers.ParseQuery(query); request.DeviceProfileId = values["DeviceProfileId"].ToString(); request.DeviceId = values["DeviceId"].ToString(); diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 09806ed9c2..9f23b2420f 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Services } - public QueryParamCollection(IDictionary headers) + public QueryParamCollection(IHeaderDictionary headers) { foreach (var pair in headers) { @@ -24,38 +24,6 @@ namespace MediaBrowser.Model.Services } } - public QueryParamCollection(Microsoft.AspNetCore.Http.IHeaderDictionary headers) - { - foreach (var pair in headers) - { - Add(pair.Key, pair.Value); - } - } - - // TODO remove this shit - public QueryParamCollection(WebHeaderCollection webHeaderCollection) - { - foreach (var key in webHeaderCollection.AllKeys) - { - foreach (var value in webHeaderCollection.GetValues(key) ?? Array.Empty()) - { - Add(key, value); - } - } - } - - // TODO remove this shit - public QueryParamCollection(NameValueCollection nameValueCollection) - { - foreach (var key in nameValueCollection.AllKeys) - { - foreach (var value in nameValueCollection.GetValues(key) ?? Array.Empty()) - { - Add(key, value); - } - } - } - public QueryParamCollection(IQueryCollection queryCollection) { foreach (var pair in queryCollection) @@ -74,21 +42,6 @@ namespace MediaBrowser.Model.Services return StringComparer.OrdinalIgnoreCase; } - public string GetKey(int index) - { - return this[index].Name; - } - - public string Get(int index) - { - return this[index].Value; - } - - public virtual string[] GetValues(int index) - { - return new[] { Get(index) }; - } - /// /// Adds a new query parameter. /// @@ -129,16 +82,6 @@ namespace MediaBrowser.Model.Services Add(key, value); } - /// - /// Removes all parameters of the given name. - /// - /// The number of parameters that were removed - /// is null. - public virtual int Remove(string name) - { - return RemoveAll(p => p.Name == name); - } - public string Get(string name) { var stringComparison = GetStringComparison(); From 27e7e792b3d95912787c613f849548809d48f6b1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 14:23:39 +0100 Subject: [PATCH 24/59] Replace some usage of QueryParamCollection --- Emby.Dlna/Api/DlnaServerService.cs | 10 +-- .../ConnectionManager/ConnectionManager.cs | 3 +- .../ContentDirectory/ContentDirectory.cs | 2 +- Emby.Dlna/ControlRequest.cs | 5 +- Emby.Dlna/DlnaManager.cs | 17 ++-- Emby.Dlna/IUpnpService.cs | 5 +- .../MediaReceiverRegistrar.cs | 3 +- .../HttpServer/HttpListenerHost.cs | 7 +- .../HttpServer/HttpResultFactory.cs | 19 ++--- .../HttpServer/WebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 3 +- .../Services/ServiceHandler.cs | 2 +- .../Session/SessionWebSocketListener.cs | 3 +- .../SocketSharp/RequestMono.cs | 2 - .../SocketSharp/WebSocketSharpListener.cs | 6 +- .../SocketSharp/WebSocketSharpRequest.cs | 12 +-- .../SocketSharp/WebSocketSharpResponse.cs | 83 +------------------ .../Playback/BaseStreamingService.cs | 6 +- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 5 +- .../Net/IWebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 6 +- MediaBrowser.Model/Services/IHttpRequest.cs | 5 -- MediaBrowser.Model/Services/IHttpResponse.cs | 12 --- MediaBrowser.Model/Services/IRequest.cs | 21 ++--- .../Services/QueryParamCollection.cs | 20 ----- 25 files changed, 66 insertions(+), 197 deletions(-) diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 68bf801637..8bf3797f85 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -136,7 +136,7 @@ namespace Emby.Dlna.Api { var url = Request.AbsoluteUri; var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress); var cacheLength = TimeSpan.FromDays(1); var cacheKey = Request.RawUrl.GetMD5(); @@ -147,21 +147,21 @@ namespace Emby.Dlna.Api public object Get(GetContentDirectory request) { - var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ContentDirectory.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetMediaReceiverRegistrar request) { - var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); + var xml = MediaReceiverRegistrar.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetConnnectionManager request) { - var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ConnectionManager.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } @@ -193,7 +193,7 @@ namespace Emby.Dlna.Api return service.ProcessControlRequest(new ControlRequest { - Headers = Request.Headers.ToDictionary(), + Headers = Request.Headers, InputXml = requestStream, TargetServerUuId = id, RequestedUrl = Request.AbsoluteUri diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index cc427f2a15..e138b91d6a 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -24,7 +23,7 @@ namespace Emby.Dlna.ConnectionManager XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ConnectionManagerXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index b0fec90e69..867e6112fd 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory } } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ContentDirectoryXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index afd9a0b874..907d437f85 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.IO; +using Microsoft.AspNetCore.Http; namespace Emby.Dlna { public class ControlRequest { - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public Stream InputXml { get; set; } @@ -15,7 +16,7 @@ namespace Emby.Dlna public ControlRequest() { - Headers = new Dictionary(); + Headers = new HeaderDictionary(); } } } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d274516..770a901521 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -17,7 +17,9 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna { @@ -205,16 +207,13 @@ namespace Emby.Dlna } } - public DeviceProfile GetProfile(IDictionary headers) + public DeviceProfile GetProfile(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException(nameof(headers)); } - // Convert to case insensitive - headers = new Dictionary(headers, StringComparer.OrdinalIgnoreCase); - var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); if (profile != null) @@ -230,12 +229,12 @@ namespace Emby.Dlna return profile; } - private bool IsMatch(IDictionary headers, DeviceIdentification profileInfo) + private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo) { return profileInfo.Headers.Any(i => IsMatch(headers, i)); } - private bool IsMatch(IDictionary headers, HttpHeaderInfo header) + private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) { // Handle invalid user setup if (string.IsNullOrEmpty(header.Name)) @@ -243,14 +242,14 @@ namespace Emby.Dlna return false; } - if (headers.TryGetValue(header.Name, out string value)) + if (headers.TryGetValue(header.Name, out StringValues value)) { switch (header.Match) { case HeaderMatchType.Equals: return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); case HeaderMatchType.Substring: - var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; + var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: @@ -493,7 +492,7 @@ namespace Emby.Dlna internal string Path { get; set; } } - public string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress) + public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { var profile = GetProfile(headers) ?? GetDefaultProfile(); diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs index ab8aa46192..ae90e95c79 100644 --- a/Emby.Dlna/IUpnpService.cs +++ b/Emby.Dlna/IUpnpService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace Emby.Dlna { public interface IUpnpService @@ -7,9 +5,8 @@ namespace Emby.Dlna /// /// Gets the content directory XML. /// - /// The headers. /// System.String. - string GetServiceXml(IDictionary headers); + string GetServiceXml(); /// /// Processes the control request. diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 2b84528eab..9c6022b6cb 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -3,6 +3,7 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -19,7 +20,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new MediaReceiverRegistrarXmlBuilder().GetXml(); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2abc6c2f4f..70753e5634 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryParamCollection() + QueryString = e.QueryString ?? new QueryCollection() }; connection.Closed += Connection_Closed; @@ -606,8 +607,8 @@ namespace Emby.Server.Implementations.HttpServer } finally { - httpRes.Close(); - + // TODO + httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 09cdbc3c26..52c8221f68 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -16,6 +16,8 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using IRequest = MediaBrowser.Model.Services.IRequest; using MimeTypes = MediaBrowser.Model.Net.MimeTypes; @@ -246,9 +248,9 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"]; + var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); - if (acceptEncoding != null) + if (string.IsNullOrEmpty(acceptEncoding)) { //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // return "br"; @@ -424,12 +426,12 @@ namespace Emby.Server.Implementations.HttpServer /// private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, StaticResultOptions options) { - bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; + bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1; AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified); if (!noCache) { - DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader); + DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader); if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified)) { @@ -530,7 +532,7 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(StringComparer.OrdinalIgnoreCase); var contentType = options.ContentType; - if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since"))) + if (!StringValues.IsNullOrEmpty(requestContext.Headers[HeaderNames.IfModifiedSince])) { // See if the result is already cached in the browser var result = GetCachedResult(requestContext, options.ResponseHeaders, options); @@ -548,7 +550,7 @@ namespace Emby.Server.Implementations.HttpServer AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); AddAgeHeader(responseHeaders, options.DateLastModified); - var rangeHeader = requestContext.Headers.Get("Range"); + var rangeHeader = requestContext.Headers["Range"]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -609,11 +611,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Adds the caching responseHeaders. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index e9d0bac74d..2bf460bd11 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -8,6 +8,7 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using UtfUnknown; @@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 666f1f6011..e3047d3926 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,6 +1,7 @@ using System; using System.Net.WebSockets; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.Net { @@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets the web socket. /// diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 7e836e22c3..3c8adfc983 100644 --- a/Emby.Server.Implementations/Services/ServiceHandler.cs +++ b/Emby.Server.Implementations/Services/ServiceHandler.cs @@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.Services { if (name == null) continue; //thank you ASP.NET - var values = request.QueryString.GetValues(name); + var values = request.QueryString[name]; if (values.Count == 1) { map[name] = values[0]; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 24903f5e8c..a551433ed9 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Session @@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Session } } - private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint) + private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint) { if (queryString == null) { diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 113f76b10b..f73adc5ff8 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -118,8 +118,6 @@ namespace Emby.Server.Implementations.SocketSharp public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } protected bool validate_form { get; set; } protected bool checked_form { get; set; } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 77469244b9..9f046c3fd0 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -52,12 +52,10 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.Query); - var connectingArgs = new WebSocketConnectingEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, Endpoint = endpoint }; @@ -73,7 +71,7 @@ using Microsoft.Extensions.Logging; WebSocketConnected(new WebSocketConnectEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, WebSocket = socket, Endpoint = endpoint }); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bddccf68ba..24fd36062f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; 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 Emby.Server.Implementations.SocketSharp @@ -21,7 +20,7 @@ namespace Emby.Server.Implementations.SocketSharp public partial class WebSocketSharpRequest : IHttpRequest { private readonly HttpRequest request; - private readonly IHttpResponse response; + private readonly IResponse response; public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) { @@ -34,11 +33,9 @@ namespace Emby.Server.Implementations.SocketSharp public HttpRequest HttpRequest => request; - public object OriginalRequest => request; - public IResponse Response => response; - public IHttpResponse HttpResponse => response; + public IResponse HttpResponse => response; public string OperationName { get; set; } @@ -396,10 +393,9 @@ namespace Emby.Server.Implementations.SocketSharp public string UserAgent => request.Headers[HeaderNames.UserAgent]; - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + public IHeaderDictionary Headers => request.Headers; - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + public IQueryCollection QueryString => request.Query; public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index f9ecb52a55..c4fbaddd3e 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -1,23 +1,18 @@ 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.AspNetCore.Http; using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; namespace Emby.Server.Implementations.SocketSharp { - public class WebSocketSharpResponse : IHttpResponse + public class WebSocketSharpResponse : IResponse { private readonly ILogger _logger; @@ -51,42 +46,7 @@ namespace Emby.Server.Implementations.SocketSharp 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 IHeaderDictionary Headers => _response.Headers; public void AddHeader(string name, string value) { @@ -111,51 +71,14 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.Body; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - public bool IsClosed { get; - private set; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); + set; } public bool SendChunked { get; set; } - 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) { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index a6be071b84..ae259a4f59 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -609,12 +609,12 @@ namespace MediaBrowser.Api.Playback { foreach (var param in Request.QueryString) { - if (char.IsLower(param.Name[0])) + if (char.IsLower(param.Key[0])) { // This was probably not parsed initially and should be a StreamOptions // TODO: This should be incorporated either in the lower framework for parsing requests // or the generated URL should correctly serialize it - request.StreamOptions[param.Name] = param.Value; + request.StreamOptions[param.Key] = param.Value; } } } @@ -867,7 +867,7 @@ namespace MediaBrowser.Api.Playback private void ApplyDeviceProfileSettings(StreamState state) { - var headers = Request.Headers.ToDictionary(); + var headers = Request.Headers; if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId)) { diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index a6ee7c5050..41a7686a37 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Dlna { @@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Dlna /// /// The headers. /// DeviceProfile. - DeviceProfile GetProfile(IDictionary headers); + DeviceProfile GetProfile(IHeaderDictionary headers); /// /// Gets the default profile. @@ -64,7 +65,7 @@ namespace MediaBrowser.Controller.Dlna /// The server uu identifier. /// The server address. /// System.String. - string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress); + string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress); /// /// Gets the icon. diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index a09b2f7a22..566897b31f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,7 +36,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - QueryParamCollection QueryString { get; set; } + IQueryCollection QueryString { get; set; } /// /// Gets or sets the receive action. diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs index f26b764bb3..107e674216 100644 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs @@ -1,5 +1,7 @@ using System; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; namespace MediaBrowser.Controller.Net { @@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets a value indicating whether [allow connection]. /// @@ -31,7 +33,7 @@ namespace MediaBrowser.Controller.Net public WebSocketConnectingEventArgs() { - QueryString = new QueryParamCollection(); + QueryString = new QueryCollection(); AllowConnection = true; } } diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 579f80c968..50c6076f30 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -2,11 +2,6 @@ namespace MediaBrowser.Model.Services { public interface IHttpRequest : IRequest { - /// - /// The HttpResponse - /// - IHttpResponse HttpResponse { get; } - /// /// The HTTP Verb /// diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs index a8b79f3949..b99d12525d 100644 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ b/MediaBrowser.Model/Services/IHttpResponse.cs @@ -4,17 +4,5 @@ namespace MediaBrowser.Model.Services { public interface IHttpResponse : IResponse { - //ICookies Cookies { get; } - - /// - /// Adds a new Set-Cookie instruction to Response - /// - /// - void SetCookie(Cookie cookie); - - /// - /// Removes all pending Set-Cookie instructions - /// - void ClearCookies(); } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 0fd4ea37b1..edb5a2509b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,20 +1,15 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { public interface IRequest { - /// - /// The underlying ASP.NET or HttpListener HttpRequest - /// - object OriginalRequest { get; } - IResponse Response { get; } /// @@ -51,9 +46,9 @@ namespace MediaBrowser.Model.Services /// Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } - QueryParamCollection QueryString { get; } + IQueryCollection QueryString { get; } Task GetFormData(); @@ -122,21 +117,15 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// Signal that this response has been handled and no more processing should be done. - /// When used in a request or response filter, no more filters or processing is done on this request. - /// - void Close(); - /// /// Gets a value indicating whether this instance is closed. /// - bool IsClosed { get; } + bool IsClosed { get; set; } //Add Metadata to Response Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 9f23b2420f..4631a3b633 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; -using System.Net; using MediaBrowser.Model.Dto; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -13,23 +10,6 @@ namespace MediaBrowser.Model.Services { public QueryParamCollection() { - - } - - public QueryParamCollection(IHeaderDictionary headers) - { - foreach (var pair in headers) - { - Add(pair.Key, pair.Value); - } - } - - public QueryParamCollection(IQueryCollection queryCollection) - { - foreach (var pair in queryCollection) - { - Add(pair.Key, pair.Value); - } } private static StringComparison GetStringComparison() From 1e97e4d462b13baace363be5304bec70940a81a1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 14:29:44 +0100 Subject: [PATCH 25/59] Remove IHttpResponse --- MediaBrowser.Model/Services/IHttpResponse.cs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 MediaBrowser.Model/Services/IHttpResponse.cs diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs deleted file mode 100644 index b99d12525d..0000000000 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Net; - -namespace MediaBrowser.Model.Services -{ - public interface IHttpResponse : IResponse - { - } -} From 647adc51c8681113afbc2c45b7637cc2c50f11f0 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 19:55:25 +0100 Subject: [PATCH 26/59] Fix query log --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 70753e5634..fd46c00578 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString) + ? $"{pagePathWithoutQueryString}?{newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value))}" : pagePathWithoutQueryString; } From c0b95dbc791acae1c28bf50fb3a33c06935e7192 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 20:11:40 +0100 Subject: [PATCH 27/59] Fix query log for real --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index fd46c00578..dbfb5e2438 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value))}" + ? $"{pagePathWithoutQueryString}?{string.Join("&", newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value)))}" : pagePathWithoutQueryString; } From e47d12198556abfbfb57d2c1a9e45f6f7230bf63 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 21:40:47 +0100 Subject: [PATCH 28/59] Fix websockets --- .../SocketSharp/SharpWebSocket.cs | 5 +++++ .../SocketSharp/WebSocketSharpListener.cs | 13 +++++++++---- .../Net/BasePeriodicWebSocketListener.cs | 13 ++----------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index dcbfb80487..eebbe8b780 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -85,6 +85,11 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { _cancellationTokenSource.Cancel(); + if (_webSocket.State == WebSocketState.Open) + { + _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", + CancellationToken.None); + } } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 9f046c3fd0..c5abad5d33 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -76,18 +76,23 @@ using Microsoft.Extensions.Logging; Endpoint = endpoint }); - var buffer = WebSocket.CreateClientBuffer(4096, 4096); WebSocketReceiveResult result; var message = new List(); do { + var buffer = WebSocket.CreateServerBuffer(4096); result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); - socket.OnReceiveBytes(buffer.Array); message.AddRange(buffer.Array.Take(result.Count)); - } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); - socket.OnReceiveBytes(message.ToArray()); + if (result.EndOfMessage) + { + socket.OnReceiveBytes(message.ToArray()); + message.Clear(); + } + } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); + + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, _disposeCancellationToken); socket.Dispose(); diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 4242a00e21..d2d0fbbc6c 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -80,12 +80,7 @@ namespace MediaBrowser.Controller.Net protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected virtual bool SendOnTimer => false; - - protected virtual void ParseMessageParams(string[] values) - { - - } + protected bool SendOnTimer => false; /// /// Starts sending messages over a web socket @@ -98,11 +93,6 @@ namespace MediaBrowser.Controller.Net var dueTimeMs = long.Parse(vals[0], UsCulture); var periodMs = long.Parse(vals[1], UsCulture); - if (vals.Length > 2) - { - ParseMessageParams(vals.Skip(2).ToArray()); - } - var cancellationTokenSource = new CancellationTokenSource(); Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); @@ -250,6 +240,7 @@ namespace MediaBrowser.Controller.Net { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); + connection.Item1.Dispose(); var timer = connection.Item3; if (timer != null) From dab8e150520f5a76d875a926530632783e17006f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 21:48:28 +0100 Subject: [PATCH 29/59] Check websocket state before closing --- .../SocketSharp/WebSocketSharpListener.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index c5abad5d33..9422673f52 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -93,8 +93,12 @@ using Microsoft.Extensions.Logging; } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + if (webSocketContext.State == WebSocketState.Open) + { + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, _disposeCancellationToken); + } + socket.Dispose(); } else From 71ed8409446774c395b339728e2436c3b30536d8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 22:09:22 +0100 Subject: [PATCH 30/59] Simplify websocket listeners --- .../ScheduledTasksWebSocketListener.cs | 3 +- .../Session/SessionInfoWebSocketListener.cs | 3 +- .../System/ActivityLogWebSocketListener.cs | 3 +- .../Net/BasePeriodicWebSocketListener.cs | 74 +++---------------- 4 files changed, 13 insertions(+), 70 deletions(-) diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs index b0c4b29c17..d24a187430 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -58,9 +58,8 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Gets the data to send. /// - /// The state. /// Task{IEnumerable{TaskInfo}}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) + protected override Task> GetDataToSend() { return Task.FromResult(TaskManager.ScheduledTasks .OrderBy(i => i.Name) diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index beb2fb11d0..b79e9f84b1 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -79,9 +79,8 @@ namespace MediaBrowser.Api.Session /// /// Gets the data to send. /// - /// The state. /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) + protected override Task> GetDataToSend() { return Task.FromResult(_sessionManager.Sessions); } diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs index 43f3c5a223..a036619b81 100644 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs @@ -38,9 +38,8 @@ namespace MediaBrowser.Api.System /// /// Gets the data to send. /// - /// The state. /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken CancellationToken) + protected override Task> GetDataToSend() { return Task.FromResult(new List()); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index d2d0fbbc6c..5a6a0ea31e 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -22,8 +22,8 @@ namespace MediaBrowser.Controller.Net /// /// The _active connections /// - protected readonly List> ActiveConnections = - new List>(); + protected readonly List> ActiveConnections = + new List>(); /// /// Gets the name. @@ -34,9 +34,8 @@ namespace MediaBrowser.Controller.Net /// /// Gets the data to send. /// - /// The state. /// Task{`1}. - protected abstract Task GetDataToSend(TStateType state, CancellationToken cancellationToken); + protected abstract Task GetDataToSend(); /// /// The logger @@ -80,8 +79,6 @@ namespace MediaBrowser.Controller.Net protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected bool SendOnTimer => false; - /// /// Starts sending messages over a web socket /// @@ -97,10 +94,6 @@ namespace MediaBrowser.Controller.Net Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); - var timer = SendOnTimer ? - new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) : - null; - var state = new TStateType { IntervalMs = periodMs, @@ -109,47 +102,13 @@ namespace MediaBrowser.Controller.Net lock (ActiveConnections) { - ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state)); - } - - if (timer != null) - { - timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs)); - } - } - - /// - /// Timers the callback. - /// - /// The state. - private void TimerCallback(object state) - { - var connection = (IWebSocketConnection)state; - - Tuple tuple; - - lock (ActiveConnections) - { - tuple = ActiveConnections.FirstOrDefault(c => c.Item1 == connection); + ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, state)); } - - if (tuple == null) - { - return; - } - - if (connection.State != WebSocketState.Open || tuple.Item2.IsCancellationRequested) - { - DisposeConnection(tuple); - return; - } - - SendData(tuple); } protected void SendData(bool force) { - Tuple[] tuples; + Tuple[] tuples; lock (ActiveConnections) { @@ -158,7 +117,7 @@ namespace MediaBrowser.Controller.Net { if (c.Item1.State == WebSocketState.Open && !c.Item2.IsCancellationRequested) { - var state = c.Item4; + var state = c.Item3; if (force || (DateTime.UtcNow - state.DateLastSendUtc).TotalMilliseconds >= state.IntervalMs) { @@ -177,17 +136,17 @@ namespace MediaBrowser.Controller.Net } } - private async void SendData(Tuple tuple) + private async void SendData(Tuple tuple) { var connection = tuple.Item1; try { - var state = tuple.Item4; + var state = tuple.Item3; var cancellationToken = tuple.Item2.Token; - var data = await GetDataToSend(state, cancellationToken).ConfigureAwait(false); + var data = await GetDataToSend().ConfigureAwait(false); if (data != null) { @@ -236,24 +195,11 @@ namespace MediaBrowser.Controller.Net /// Disposes the connection. /// /// The connection. - private void DisposeConnection(Tuple connection) + private void DisposeConnection(Tuple connection) { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); connection.Item1.Dispose(); - var timer = connection.Item3; - - if (timer != null) - { - try - { - timer.Dispose(); - } - catch (ObjectDisposedException) - { - //TODO Investigate and properly fix. - } - } try { From fb1de5a9213f7da98ed15a6975201d6bca3537d4 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:22:55 +0100 Subject: [PATCH 31/59] Remove more cruft and add the beginnings of a socket middleware --- .../ApplicationHost.cs | 2 + .../HttpServer/WebSocketConnection.cs | 36 +--------- .../Middleware/WebSocketMiddleware.cs | 36 ++++++++++ Emby.Server.Implementations/Net/IWebSocket.cs | 5 -- .../SocketSharp/WebSocketSharpListener.cs | 65 +++++++------------ .../WebSocket/WebSocketManager.cs | 7 ++ .../WebSockets/WebSocketManager.cs | 22 +++++++ .../MediaBrowser.Controller.csproj | 8 ++- 8 files changed, 97 insertions(+), 84 deletions(-) create mode 100644 Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs create mode 100644 Emby.Server.Implementations/WebSocket/WebSocketManager.cs create mode 100644 Emby.Server.Implementations/WebSockets/WebSocketManager.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0b4a2fd30e..e558b43544 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -35,6 +35,7 @@ using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Library; using Emby.Server.Implementations.LiveTv; using Emby.Server.Implementations.Localization; +using Emby.Server.Implementations.Middleware; using Emby.Server.Implementations.Net; using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Reflection; @@ -641,6 +642,7 @@ namespace Emby.Server.Implementations app.UseWebSockets(); app.UseResponseCompression(); + // TODO app.UseMiddleware(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); }) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2bf460bd11..54a16040f7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -102,12 +102,6 @@ namespace Emby.Server.Implementations.HttpServer _socket = socket; _socket.OnReceiveBytes = OnReceiveInternal; - var memorySocket = socket as IMemoryWebSocket; - if (memorySocket != null) - { - memorySocket.OnReceiveMemoryBytes = OnReceiveInternal; - } - RemoteEndPoint = remoteEndPoint; _logger = logger; @@ -143,34 +137,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// Called when [receive]. - /// - /// The memory block. - /// The length of the memory block. - private void OnReceiveInternal(Memory memory, int length) - { - LastActivityDate = DateTime.UtcNow; - - if (OnReceive == null) - { - return; - } - - var bytes = memory.Slice(0, length).ToArray(); - - var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName; - - if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)) - { - OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length)); - } - else - { - OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length)); - } - } - private void OnReceiveInternal(string message) { LastActivityDate = DateTime.UtcNow; @@ -194,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer var info = new WebSocketMessageInfo { MessageType = stub.MessageType, - Data = stub.Data == null ? null : stub.Data.ToString(), + Data = stub.Data?.ToString(), Connection = this }; diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs new file mode 100644 index 0000000000..a1d0e77d65 --- /dev/null +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using WebSocketManager = Emby.Server.Implementations.WebSockets.WebSocketManager; + +namespace Emby.Server.Implementations.Middleware +{ + public class WebSocketMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly WebSocketManager _webSocketManager; + + public WebSocketMiddleware(RequestDelegate next, ILogger logger, WebSocketManager webSocketManager) + { + _next = next; + _logger = logger; + _webSocketManager = webSocketManager; + } + + public async Task Invoke(HttpContext httpContext) + { + _logger.LogInformation("Handling request: " + httpContext.Request.Path); + + if (httpContext.WebSockets.IsWebSocketRequest) + { + var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + _webSocketManager.AddSocket(webSocketContext); + } + else + { + await _next.Invoke(httpContext); + } + } + } +} diff --git a/Emby.Server.Implementations/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs index 4671de07c5..4d160aa66f 100644 --- a/Emby.Server.Implementations/Net/IWebSocket.cs +++ b/Emby.Server.Implementations/Net/IWebSocket.cs @@ -45,9 +45,4 @@ namespace Emby.Server.Implementations.Net /// Task. Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken); } - - public interface IMemoryWebSocket - { - Action, int> OnReceiveMemoryBytes { get; set; } - } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 9422673f52..5ddd316477 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -33,8 +33,6 @@ using Microsoft.Extensions.Logging; public Func ErrorHandler { get; set; } public Func RequestHandler { get; set; } - public Action WebSocketConnecting { get; set; } - public Action WebSocketConnected { get; set; } private static void LogRequest(ILogger logger, HttpRequest request) @@ -52,60 +50,41 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var connectingArgs = new WebSocketConnectingEventArgs + var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + var socket = new SharpWebSocket(webSocketContext, _logger); + + WebSocketConnected(new WebSocketConnectEventArgs { Url = url, QueryString = ctx.Request.Query, + WebSocket = socket, Endpoint = endpoint - }; + }); - WebSocketConnecting?.Invoke(connectingArgs); + WebSocketReceiveResult result; + var message = new List(); - if (connectingArgs.AllowConnection) + do { - _logger.LogDebug("Web socket connection allowed"); + var buffer = WebSocket.CreateServerBuffer(4096); + result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); + message.AddRange(buffer.Array.Take(result.Count)); - var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - var socket = new SharpWebSocket(webSocketContext, _logger); - - WebSocketConnected(new WebSocketConnectEventArgs + if (result.EndOfMessage) { - Url = url, - QueryString = ctx.Request.Query, - WebSocket = socket, - Endpoint = endpoint - }); - - WebSocketReceiveResult result; - var message = new List(); - - do - { - var buffer = WebSocket.CreateServerBuffer(4096); - result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); - message.AddRange(buffer.Array.Take(result.Count)); - - if (result.EndOfMessage) - { - socket.OnReceiveBytes(message.ToArray()); - message.Clear(); - } - } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - - - if (webSocketContext.State == WebSocketState.Open) - { - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + socket.OnReceiveBytes(message.ToArray()); + message.Clear(); } + } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - socket.Dispose(); - } - else + + if (webSocketContext.State == WebSocketState.Open) { - _logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, _disposeCancellationToken); } + + socket.Dispose(); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs new file mode 100644 index 0000000000..7472820cf2 --- /dev/null +++ b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs @@ -0,0 +1,7 @@ +namespace Emby.Server.Implementations.WebSocket +{ + public class WebSocketManager + { + + } +} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs new file mode 100644 index 0000000000..7e74a45277 --- /dev/null +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Concurrent; +using System.Net.WebSockets; + +namespace Emby.Server.Implementations.WebSockets +{ + public class WebSocketManager + { + private readonly ConcurrentDictionary _activeWebSockets; + + public WebSocketManager() + { + _activeWebSockets = new ConcurrentDictionary(); + } + + public void AddSocket(WebSocket webSocket) + { + var guid = Guid.NewGuid(); + _activeWebSockets.TryAdd(guid, webSocket); + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 01893f1b55..81e255d525 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -16,6 +16,12 @@ + + + ..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll + + + netstandard2.0 false From 588a13377c347cbd56462c64b80f0b9a563e534f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:24:43 +0100 Subject: [PATCH 32/59] Remove a file that shouldn't have been added --- Emby.Server.Implementations/WebSocket/WebSocketManager.cs | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 Emby.Server.Implementations/WebSocket/WebSocketManager.cs diff --git a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs deleted file mode 100644 index 7472820cf2..0000000000 --- a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Emby.Server.Implementations.WebSocket -{ - public class WebSocketManager - { - - } -} From 1ac282b12e20abc91402015369dc17543604f9fb Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:39:20 +0100 Subject: [PATCH 33/59] Call SharpWebSocket's Closed event handler before disposing --- Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index eebbe8b780..62b16ed8c8 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -90,6 +90,7 @@ namespace Emby.Server.Implementations.SocketSharp _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None); } + Closed?.Invoke(this, EventArgs.Empty); } _disposed = true; From 6bdb5debd2492b71d11f9628889b8c29b6321a77 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 1 Mar 2019 14:08:51 +0100 Subject: [PATCH 34/59] Add some websocket manager boilerplate --- .../Middleware/WebSocketMiddleware.cs | 5 +- .../WebSockets/WebSocketHandler.cs | 10 +++ .../WebSockets/WebSocketManager.cs | 90 +++++++++++++++++-- 3 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Emby.Server.Implementations/WebSockets/WebSocketHandler.cs diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs index a1d0e77d65..268bf4042d 100644 --- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Middleware if (httpContext.WebSockets.IsWebSocketRequest) { var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - _webSocketManager.AddSocket(webSocketContext); + if (webSocketContext != null) + { + await _webSocketManager.OnWebSocketConnected(webSocketContext); + } } else { diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs new file mode 100644 index 0000000000..70b9e85aa9 --- /dev/null +++ b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using MediaBrowser.Model.Net; + +namespace Emby.Server.Implementations.WebSockets +{ + public interface IWebSocketHandler + { + Task ProcessMessage(WebSocketMessage message); + } +} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 7e74a45277..888f2f0fce 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -1,22 +1,100 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Net; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using UtfUnknown; namespace Emby.Server.Implementations.WebSockets { public class WebSocketManager { - private readonly ConcurrentDictionary _activeWebSockets; + private readonly IWebSocketHandler[] _webSocketHandlers; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + private const int BufferSize = 4096; - public WebSocketManager() + public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger logger) { - _activeWebSockets = new ConcurrentDictionary(); + _webSocketHandlers = webSocketHandlers; + _jsonSerializer = jsonSerializer; + _logger = logger; } - public void AddSocket(WebSocket webSocket) + public async Task OnWebSocketConnected(WebSocket webSocket) { - var guid = Guid.NewGuid(); - _activeWebSockets.TryAdd(guid, webSocket); + var taskCompletionSource = new TaskCompletionSource(); + var cancellationToken = new CancellationTokenSource().Token; + WebSocketReceiveResult result; + var message = new List(); + + do + { + var buffer = WebSocket.CreateServerBuffer(BufferSize); + result = await webSocket.ReceiveAsync(buffer, cancellationToken); + message.AddRange(buffer.Array.Take(result.Count)); + + if (result.EndOfMessage) + { + await ProcessMessage(message.ToArray(), taskCompletionSource); + message.Clear(); + } + } while (!taskCompletionSource.Task.IsCompleted && + webSocket.State == WebSocketState.Open && + result.MessageType != WebSocketMessageType.Close); + + if (webSocket.State == WebSocketState.Open) + { + await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, cancellationToken); + } + } + + public async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) + { + var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; + var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) + ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) + : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); + + // All messages are expected to be json + if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message); + return; + } + + try + { + var info = _jsonSerializer.DeserializeFromString>(message); + + _logger.LogDebug("Websocket message received: {0}", info.MessageType); + + var tasks = _webSocketHandlers.Select(handler => Task.Run(() => + { + try + { + handler.ProcessMessage(info).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "{0} failed processing WebSocket message {1}", handler.GetType().Name, info.MessageType ?? string.Empty); + } + })); + + await Task.WhenAll(tasks); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing web socket message"); + } } } } From e823c11b46ccf0473aa72cb52cd5a3a9f977e61b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 08:29:23 +0100 Subject: [PATCH 35/59] Add certificate to https and minor cleanup --- .../ApplicationHost.cs | 26 +++++++++---------- .../HttpServer/HttpListenerHost.cs | 11 -------- .../WebSockets/WebSocketHandler.cs | 2 +- .../WebSockets/WebSocketManager.cs | 8 +++--- Jellyfin.Server/CoreAppHost.cs | 2 -- 5 files changed, 18 insertions(+), 31 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e558b43544..05d8ee410c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -626,9 +626,12 @@ namespace Emby.Server.Implementations { options.Listen(IPAddress.Any, HttpPort); options.Listen(IPAddress.Loopback, HttpPort); - // TODO certs - options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); - options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + + if (CertificateInfo != null) + { + options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + } }) .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) .ConfigureServices(services => @@ -927,11 +930,9 @@ namespace Emby.Server.Implementations } } - protected virtual bool SupportsDualModeSockets => true; - - private X509Certificate GetCertificate(CertificateInfo info) + private X509Certificate2 GetCertificate(CertificateInfo info) { - var certificateLocation = info == null ? null : info.Path; + var certificateLocation = info?.Path; if (string.IsNullOrWhiteSpace(certificateLocation)) { @@ -1004,7 +1005,7 @@ namespace Emby.Server.Implementations return info; } - protected virtual FFMpegInfo GetFFMpegInfo() + protected FFMpegInfo GetFFMpegInfo() { return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) .GetFFMpegInfo(StartupOptions); @@ -1085,7 +1086,7 @@ namespace Emby.Server.Implementations /// private void SetStaticProperties() { - ((SqliteItemRepository)ItemRepository).ImageProcessor = ImageProcessor; + ItemRepository.ImageProcessor = ImageProcessor; // For now there's no real way to inject these properly BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem"); @@ -1211,15 +1212,12 @@ namespace Emby.Server.Implementations AllConcreteTypes = GetComposablePartAssemblies() .SelectMany(x => x.ExportedTypes) - .Where(type => - { - return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType; - }) + .Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType) .ToArray(); } private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate Certificate { get; private set; } + protected X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dbfb5e2438..263fcdbe9e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -855,16 +855,5 @@ namespace Emby.Server.Implementations.HttpServer { Dispose(true); } - - public void StartServer(string[] urlPrefixes, IHttpListener httpListener) - { - UrlPrefixes = urlPrefixes; - - _listener = httpListener; - - _listener.WebSocketConnected = OnWebSocketConnected; - _listener.ErrorHandler = ErrorHandler; - _listener.RequestHandler = RequestHandler; - } } } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs index 70b9e85aa9..eb18774408 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs @@ -5,6 +5,6 @@ namespace Emby.Server.Implementations.WebSockets { public interface IWebSocketHandler { - Task ProcessMessage(WebSocketMessage message); + Task ProcessMessage(WebSocketMessage message, TaskCompletionSource taskCompletionSource); } } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 888f2f0fce..5db2c8da73 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -35,6 +35,7 @@ namespace Emby.Server.Implementations.WebSockets WebSocketReceiveResult result; var message = new List(); + // Keep listening for incoming messages, otherwise the socket closes automatically do { var buffer = WebSocket.CreateServerBuffer(BufferSize); @@ -57,7 +58,7 @@ namespace Emby.Server.Implementations.WebSockets } } - public async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) + private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) { var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) @@ -81,11 +82,12 @@ namespace Emby.Server.Implementations.WebSockets { try { - handler.ProcessMessage(info).ConfigureAwait(false); + handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false); } catch (Exception ex) { - _logger.LogError(ex, "{0} failed processing WebSocket message {1}", handler.GetType().Name, info.MessageType ?? string.Empty); + _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}", + handler.GetType().Name, info.MessageType ?? string.Empty); } })); diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 9e52247900..17259c7370 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -34,8 +34,6 @@ namespace Jellyfin.Server public override bool CanSelfRestart => StartupOptions.RestartPath != null; - protected override bool SupportsDualModeSockets => true; - protected override void RestartInternal() => Program.Restart(); protected override IEnumerable GetAssembliesWithPartsInternal() From d450169964680ed249f140de8422bf415b44958c Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 08:46:17 +0100 Subject: [PATCH 36/59] Use EnableHttps instead of CertificateInfo --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 05d8ee410c..284900e870 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -627,7 +627,7 @@ namespace Emby.Server.Implementations options.Listen(IPAddress.Any, HttpPort); options.Listen(IPAddress.Loopback, HttpPort); - if (CertificateInfo != null) + if (EnableHttps) { options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); From 1cc433eabc4813fd175b8ce63b27490663cbbcee Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 13:54:14 +0100 Subject: [PATCH 37/59] Start the webhost before RunStartupTasks and fix ContentEncoding --- .../SocketSharp/WebSocketSharpRequest.cs | 25 ++++++++++++++++--- Jellyfin.Server/Program.cs | 1 - 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 24fd36062f..976f2ec065 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -408,11 +408,28 @@ namespace Emby.Server.Implementations.SocketSharp public string ContentType => request.ContentType; - private Encoding contentEncoding; - public Encoding ContentEncoding + private Encoding ContentEncoding { - get => contentEncoding ?? Encoding.GetEncoding(request.Headers[HeaderNames.ContentEncoding].ToString()); - set => contentEncoding = value; + get + { + // TODO is this necessary? + if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) + { + string postDataCharset = Headers["x-up-devcap-post-charset"]; + if (!string.IsNullOrEmpty(postDataCharset)) + { + try + { + return Encoding.GetEncoding(postDataCharset); + } + catch (ArgumentException) + { + } + } + } + + return request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8; + } } public Uri UrlReferrer => request.GetTypedHeaders().Referer; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index bfc50468f7..a43c3fea5c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -144,7 +144,6 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - appHost.Host.Run(); // TODO: read input for a stop command try From 6263b73d9c5659f2a6216dd2bea55df230ae3fcb Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 14:30:41 +0100 Subject: [PATCH 38/59] Await host startup --- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 284900e870..c2e6481af7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -650,6 +650,8 @@ namespace Emby.Server.Implementations app.Use(ExecuteHttpHandlerAsync); }) .Build(); + + await Host.StartAsync(); } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) From 040871459b572ce0dad09ae8f27938cb424b98e7 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 14:35:54 +0100 Subject: [PATCH 39/59] Remove some unused references --- .../MediaReceiverRegistrar.cs | 2 -- .../ApplicationHost.cs | 3 -- .../Emby.Server.Implementations.csproj | 4 --- Emby.Server.Implementations/Startup.cs | 34 ------------------- 4 files changed, 43 deletions(-) delete mode 100644 Emby.Server.Implementations/Startup.cs diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 9c6022b6cb..e47143c16a 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c2e6481af7..00a5f864df 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -111,13 +111,11 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; -using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations { @@ -638,7 +636,6 @@ namespace Emby.Server.Implementations { services.AddResponseCompression(); services.AddHttpContextAccessor(); - services.TryAddSingleton(); }) .Configure(app => { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4db5f2fcc..39e9ed3756 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -26,11 +26,7 @@ - - - - diff --git a/Emby.Server.Implementations/Startup.cs b/Emby.Server.Implementations/Startup.cs deleted file mode 100644 index 164c7eeaa7..0000000000 --- a/Emby.Server.Implementations/Startup.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Api; -using MediaBrowser.Controller; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations -{ - public class Startup - { - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration) => Configuration = configuration; - - // Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddRouting(); - } - - // Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - - } - } - -} From 6cc1bd544add65fdf7758c04655586c206675193 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 19:31:26 +0100 Subject: [PATCH 40/59] Fix a logging statement --- .../SocketSharp/WebSocketSharpListener.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 5ddd316477..2df826957a 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -12,8 +12,9 @@ using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; - namespace Emby.Server.Implementations.SocketSharp +namespace Emby.Server.Implementations.SocketSharp { public class WebSocketSharpListener : IHttpListener { @@ -39,7 +40,7 @@ using Microsoft.Extensions.Logging; { var url = request.GetDisplayUrl(); - logger.LogInformation("{0} {1}. UserAgent: {2}", "WS", url, request.Headers["User-Agent"].ToString()); + logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString()); } public async Task ProcessWebSocketRequest(HttpContext ctx) From 557c4d065ddba0ccc6780c6a4c7811fe45351c88 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 19:55:59 +0100 Subject: [PATCH 41/59] Review comments --- .../SocketSharp/WebSocketSharpResponse.cs | 6 +- .../MediaBrowser.Controller.csproj | 6 - MediaBrowser.sln | 374 +++++++++--------- 3 files changed, 188 insertions(+), 198 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index c4fbaddd3e..5964107651 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -71,11 +71,7 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public bool IsClosed - { - get; - set; - } + public bool IsClosed { get; set; } public bool SendChunked { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 81e255d525..5898d8f984 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -16,12 +16,6 @@ - - - ..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll - - - netstandard2.0 false diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 5edc1a25a6..b1d3fcdda8 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,187 +1,187 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - SharedVersion.cs = SharedVersion.cs - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} - EndGlobalSection - GlobalSection(AutomaticVersions) = postSolution - UpdateAssemblyVersion = True - UpdateAssemblyFileVersion = True - UpdateAssemblyInfoVersion = True - AssemblyVersionSettings = None.None.None.None - AssemblyFileVersionSettings = None.None.None.None - AssemblyInfoVersionSettings = None.None.None.None - UpdatePackageVersion = False - AssemblyInfoVersionType = SettingsVersion - InheritWinAppVersionFrom = None - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n - $0.DotNetNamingPolicy = $2 - $2.DirectoryNamespaceAssociation = PrefixedHierarchical - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal From 9020f68ce16b8184c2b9f5cefde2b966b15fc741 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 20:08:54 +0100 Subject: [PATCH 42/59] Use QueryHelpers.AddQueryString --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 263fcdbe9e..16e3c383b8 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{string.Join("&", newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value)))}" + ? QueryHelpers.AddQueryString(pagePathWithoutQueryString, newQueryString.ToDictionary(kv => kv.Key, kv => kv.Value.ToString())) : pagePathWithoutQueryString; } From 0250204f14e8a004c926e1ec263723459788a029 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 22:26:57 +0100 Subject: [PATCH 43/59] Expand todo --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 16e3c383b8..19b1b26e89 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -607,7 +607,7 @@ namespace Emby.Server.Implementations.HttpServer } finally { - // TODO + // TODO response closes automatically after the handler is done, but some functions rely on knowing if it's closed or not httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; From 17ca23d73b9dc440373abcde2df39e1cf7a50e35 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 07:41:41 +0100 Subject: [PATCH 44/59] Don't dispose the connection --- MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 5a6a0ea31e..8444125462 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -199,7 +199,8 @@ namespace MediaBrowser.Controller.Net { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); - connection.Item1.Dispose(); + // TODO disposing the connection seems to break websockets in subtle ways, so what is the purpose of this function really... + // connection.Item1.Dispose(); try { From 12df3814954a4251c30fd27ccdac89647da1a0a3 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 07:55:29 +0100 Subject: [PATCH 45/59] Grab content root from config and fix kestrel port bindings --- Emby.Server.Implementations/ApplicationHost.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 00a5f864df..09c40ea735 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -619,19 +619,23 @@ namespace Emby.Server.Implementations FindParts(); + string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(contentRoot)) + { + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + } + Host = new WebHostBuilder() .UseKestrel(options => { - options.Listen(IPAddress.Any, HttpPort); - options.Listen(IPAddress.Loopback, HttpPort); + options.ListenAnyIP(HttpPort); if (EnableHttps) { - options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); - options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); } }) - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) + .UseContentRoot(contentRoot) .ConfigureServices(services => { services.AddResponseCompression(); @@ -655,7 +659,7 @@ namespace Emby.Server.Implementations { if (!context.WebSockets.IsWebSocketRequest) { - await next(); + await next().ConfigureAwait(false); return; } @@ -665,7 +669,7 @@ namespace Emby.Server.Implementations { if (context.WebSockets.IsWebSocketRequest) { - await next(); + await next().ConfigureAwait(false); return; } From 51648a2a21e4829a66b0bff0ef1e0e23be1c6245 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 08:05:42 +0100 Subject: [PATCH 46/59] Remove unused _listener --- .../HttpServer/HttpListenerHost.cs | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 19b1b26e89..293b28e74b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -33,12 +33,8 @@ namespace Emby.Server.Implementations.HttpServer public class HttpListenerHost : IHttpServer, IDisposable { private string DefaultRedirectPath { get; set; } - - private readonly ILogger _logger; public string[] UrlPrefixes { get; private set; } - private IHttpListener _listener; - public event EventHandler> WebSocketConnected; private readonly IServerConfigurationManager _config; @@ -68,7 +64,7 @@ namespace Emby.Server.Implementations.HttpServer IHttpListener socketListener) { _appHost = applicationHost; - _logger = loggerFactory.CreateLogger("HttpServer"); + Logger = loggerFactory.CreateLogger("HttpServer"); _config = config; DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; _networkManager = networkManager; @@ -85,7 +81,7 @@ namespace Emby.Server.Implementations.HttpServer public string GlobalResponse { get; set; } - protected ILogger Logger => _logger; + protected ILogger Logger { get; } public object CreateInstance(Type type) { @@ -151,7 +147,7 @@ namespace Emby.Server.Implementations.HttpServer return; } - var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger) + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger) { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, @@ -220,11 +216,11 @@ namespace Emby.Server.Implementations.HttpServer if (logExceptionStackTrace) { - _logger.LogError(ex, "Error processing request"); + Logger.LogError(ex, "Error processing request"); } else if (logExceptionMessage) { - _logger.LogError(ex.Message); + Logger.LogError(ex.Message); } var httpRes = httpReq.Response; @@ -242,7 +238,7 @@ namespace Emby.Server.Implementations.HttpServer } catch (Exception errorEx) { - _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); + Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)"); } } @@ -285,14 +281,6 @@ namespace Emby.Server.Implementations.HttpServer } } - - if (_listener != null) - { - _logger.LogInformation("Stopping HttpListener..."); - var task = _listener.Stop(); - Task.WaitAll(task); - _logger.LogInformation("HttpListener stopped"); - } } public static string RemoveQueryStringByKey(string url, string key) @@ -613,11 +601,11 @@ namespace Emby.Server.Implementations.HttpServer var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) { - _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } else { - _logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } @@ -630,7 +618,7 @@ namespace Emby.Server.Implementations.HttpServer var pathParts = pathInfo.TrimStart('/').Split('/'); if (pathParts.Length == 0) { - _logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); + Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl); return null; } @@ -644,7 +632,7 @@ namespace Emby.Server.Implementations.HttpServer }; } - _logger.LogError("Could not find handler for {PathInfo}", pathInfo); + Logger.LogError("Could not find handler for {PathInfo}", pathInfo); return null; } @@ -696,7 +684,7 @@ namespace Emby.Server.Implementations.HttpServer ServiceController = new ServiceController(); - _logger.LogInformation("Calling ServiceStack AppHost.Init"); + Logger.LogInformation("Calling ServiceStack AppHost.Init"); var types = services.Select(r => r.GetType()).ToArray(); @@ -704,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer ResponseFilters = new Action[] { - new ResponseFilter(_logger).FilterResponse + new ResponseFilter(Logger).FilterResponse }; } @@ -834,7 +822,7 @@ namespace Emby.Server.Implementations.HttpServer return Task.CompletedTask; } - _logger.LogDebug("Websocket message received: {0}", result.MessageType); + Logger.LogDebug("Websocket message received: {0}", result.MessageType); var tasks = _webSocketListeners.Select(i => Task.Run(async () => { @@ -844,7 +832,7 @@ namespace Emby.Server.Implementations.HttpServer } catch (Exception ex) { - _logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty); + Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty); } })); From bc00617df7eee51a80302816bef9a797455e7780 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 10:26:43 +0100 Subject: [PATCH 47/59] Remove unused Brotli compressor --- Emby.Server.Implementations/ApplicationHost.cs | 7 +------ .../HttpServer/HttpResultFactory.cs | 15 +-------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 09c40ea735..deb1d3cee0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -741,7 +741,7 @@ namespace Emby.Server.Implementations ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); - HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); + HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer); serviceCollection.AddSingleton(HttpResultFactory); serviceCollection.AddSingleton(this); @@ -896,11 +896,6 @@ namespace Emby.Server.Implementations _serviceProvider = serviceCollection.BuildServiceProvider(); } - protected virtual IBrotliCompressor CreateBrotliCompressor() - { - return null; - } - public virtual string PackageRuntime => "netcore"; public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 52c8221f68..be50719d85 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -35,16 +35,13 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private IBrotliCompressor _brotliCompressor; - /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor) + public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _brotliCompressor = brotliCompressor; _logger = loggerfactory.CreateLogger("HttpResultFactory"); } @@ -350,11 +347,6 @@ namespace Emby.Server.Implementations.HttpServer private byte[] Compress(byte[] bytes, string compressionType) { - if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase)) - { - return CompressBrotli(bytes); - } - if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase)) { return Deflate(bytes); @@ -368,11 +360,6 @@ namespace Emby.Server.Implementations.HttpServer throw new NotSupportedException(compressionType); } - private byte[] CompressBrotli(byte[] bytes) - { - return _brotliCompressor.Compress(bytes); - } - private static byte[] Deflate(byte[] bytes) { // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream From 318e0d4a2449baff806afb4ee22dc0f4021ca180 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 10:27:25 +0100 Subject: [PATCH 48/59] Add GetValueOrDefault dictionary extension --- Emby.Dlna/PlayTo/PlayToController.cs | 22 +++++++++---------- .../Extensions/CollectionExtensions.cs | 14 ++++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 MediaBrowser.Common/Extensions/CollectionExtensions.cs diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 2da8bb8609..8d7d59249a 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -17,7 +18,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Services; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -849,13 +849,13 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - Dictionary values = QueryHelpers.ParseQuery(query); + Dictionary values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); - request.DeviceProfileId = values["DeviceProfileId"].ToString(); - request.DeviceId = values["DeviceId"].ToString(); - request.MediaSourceId = values["MediaSourceId"].ToString(); - request.LiveStreamId = values["LiveStreamId"].ToString(); - request.IsDirectStream = string.Equals("true", values["Static"].ToString(), StringComparison.OrdinalIgnoreCase); + request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId"); + request.DeviceId = values.GetValueOrDefault("DeviceId"); + request.MediaSourceId = values.GetValueOrDefault("MediaSourceId"); + request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); + request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); @@ -869,9 +869,9 @@ namespace Emby.Dlna.PlayTo } } - private static int? GetIntValue(IReadOnlyDictionary values, string name) + private static int? GetIntValue(IReadOnlyDictionary values, string name) { - var value = values[name].ToString(); + var value = values.GetValueOrDefault(name); if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { @@ -881,9 +881,9 @@ namespace Emby.Dlna.PlayTo return null; } - private static long GetLongValue(IReadOnlyDictionary values, string name) + private static long GetLongValue(IReadOnlyDictionary values, string name) { - var value = values[name].ToString(); + var value = values.GetValueOrDefault(name); if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { diff --git a/MediaBrowser.Common/Extensions/CollectionExtensions.cs b/MediaBrowser.Common/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000000..f7c0e3cf04 --- /dev/null +++ b/MediaBrowser.Common/Extensions/CollectionExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Common.Extensions +{ + // The MS CollectionExtensions are only available in netcoreapp + public static class CollectionExtensions + { + public static TValue GetValueOrDefault (this IReadOnlyDictionary dictionary, TKey key) + { + dictionary.TryGetValue(key, out var ret); + return ret; + } + } +} From 78742b8e4c658b1f02c9c040b8abb8ee5742bed4 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:20:28 +0100 Subject: [PATCH 49/59] Switch to HeaderNames instead of hardcoded strings (and other header related fixes) --- .../HttpClientManager/HttpClientManager.cs | 5 +- .../HttpServer/FileWriter.cs | 9 ++- .../HttpServer/HttpResultFactory.cs | 41 +++++----- .../HttpServer/RangeRequestWriter.cs | 7 +- .../HttpServer/ResponseFilter.cs | 7 +- .../Security/AuthorizationContext.cs | 3 +- .../HttpServer/StreamWriter.cs | 5 +- .../LiveTv/Listings/SchedulesDirect.cs | 5 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 3 +- .../SocketSharp/RequestMono.cs | 5 +- MediaBrowser.Api/Images/ImageService.cs | 3 +- MediaBrowser.Api/Library/LibraryService.cs | 3 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 15 ++-- .../BaseProgressiveStreamingService.cs | 81 ++++--------------- MediaBrowser.Common/Net/HttpRequestOptions.cs | 9 ++- 15 files changed, 82 insertions(+), 119 deletions(-) diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index ef82d1d9ee..1bebdd1637 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -15,6 +15,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpClientManager { @@ -179,11 +180,11 @@ namespace Emby.Server.Implementations.HttpClientManager foreach (var header in options.RequestHeaders) { - if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase)) { request.Accept = header.Value; } - else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase)) { SetUserAgent(request, header.Value); hasUserAgent = true; diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 1375089e39..303db99801 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -9,6 +9,7 @@ using Emby.Server.Implementations.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -56,10 +57,10 @@ namespace Emby.Server.Implementations.HttpServer FileSystem = fileSystem; RangeHeader = rangeHeader; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; TotalContentLength = fileSystem.GetFileInfo(path).Length; - Headers["Accept-Ranges"] = "bytes"; + Headers[HeaderNames.AcceptRanges] = "bytes"; if (string.IsNullOrWhiteSpace(rangeHeader)) { @@ -97,8 +98,8 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); - Headers["Content-Range"] = rangeString; + var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + Headers[HeaderNames.ContentRange] = rangeString; Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index be50719d85..f4fe6b611a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.HttpServer public object GetRedirectResult(string url) { var responseHeaders = new Dictionary(); - responseHeaders["Location"] = url; + responseHeaders[HeaderNames.Location] = url; var result = new HttpResult(Array.Empty(), "text/plain", HttpStatusCode.Redirect); @@ -96,9 +96,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -142,9 +142,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -186,9 +186,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; return ToOptimizedResultInternal(requestContext, result, responseHeaders); } @@ -245,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); + var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString(); if (string.IsNullOrEmpty(acceptEncoding)) { @@ -325,9 +325,9 @@ namespace Emby.Server.Implementations.HttpServer } content = Compress(content, requestedCompressionType); - responseHeaders["Content-Encoding"] = requestedCompressionType; + responseHeaders[HeaderNames.ContentEncoding] = requestedCompressionType; - responseHeaders["Vary"] = "Accept-Encoding"; + responseHeaders[HeaderNames.Vary] = HeaderNames.AcceptEncoding; var contentLength = content.Length; @@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.HttpServer AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); AddAgeHeader(responseHeaders, options.DateLastModified); - var rangeHeader = requestContext.Headers["Range"]; + var rangeHeader = requestContext.Headers[HeaderNames.Range]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -606,23 +606,23 @@ namespace Emby.Server.Implementations.HttpServer { if (noCache) { - responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; - responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; + responseHeaders[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate"; + responseHeaders[HeaderNames.Pragma] = "no-cache, no-store, must-revalidate"; return; } if (cacheDuration.HasValue) { - responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds; + responseHeaders[HeaderNames.CacheControl] = "public, max-age=" + cacheDuration.Value.TotalSeconds; } else { - responseHeaders["Cache-Control"] = "public"; + responseHeaders[HeaderNames.CacheControl] = "public"; } if (lastModifiedDate.HasValue) { - responseHeaders["Last-Modified"] = lastModifiedDate.ToString(); + responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString(); } } @@ -635,7 +635,7 @@ namespace Emby.Server.Implementations.HttpServer { if (lastDateModified.HasValue) { - responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); + responseHeaders[HeaderNames.Age] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); } } @@ -693,9 +693,4 @@ namespace Emby.Server.Implementations.HttpServer } } } - - public interface IBrotliCompressor - { - byte[] Compress(byte[] content); - } } diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8904e11d30..a0bfe5d8f9 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -66,8 +67,8 @@ namespace Emby.Server.Implementations.HttpServer this._logger = logger; ContentType = contentType; - Headers["Content-Type"] = contentType; - Headers["Accept-Ranges"] = "bytes"; + Headers[HeaderNames.ContentType] = contentType; + Headers[HeaderNames.AcceptRanges] = "bytes"; StatusCode = HttpStatusCode.PartialContent; SetRangeValues(contentLength); @@ -96,7 +97,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - Headers["Content-Range"] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; + Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) { diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs index ae6a6576e0..a53d9bf0b9 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Text; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -44,13 +45,13 @@ namespace Emby.Server.Implementations.HttpServer if (dto is IHasHeaders hasHeaders) { - if (!hasHeaders.Headers.ContainsKey("Server")) + if (!hasHeaders.Headers.ContainsKey(HeaderNames.Server)) { - hasHeaders.Headers["Server"] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50"; + hasHeaders.Headers[HeaderNames.Server] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50"; } // Content length has to be explicitly set on on HttpListenerResponse or it won't be happy - if (hasHeaders.Headers.TryGetValue("Content-Length", out string contentLength) + if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength) && !string.IsNullOrEmpty(contentLength)) { var length = long.Parse(contentLength, UsCulture); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index cab41e65b9..276312a300 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Model.Services; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer.Security { @@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.HttpServer.Security if (string.IsNullOrEmpty(auth)) { - auth = httpReq.Headers["Authorization"]; + auth = httpReq.Headers[HeaderNames.Authorization]; } return GetAuthorization(auth); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 90354385dc..66a13e20df 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.HttpServer { @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; } /// @@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 0bbffb824b..4137760d07 100644 --- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.LiveTv.Listings { @@ -638,7 +639,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings #if NETSTANDARD2_0 if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - options.RequestHeaders["Accept-Encoding"] = "deflate"; + options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; } #endif @@ -676,7 +677,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings #if NETSTANDARD2_0 if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - options.RequestHeaders["Accept-Encoding"] = "deflate"; + options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate"; } #endif diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index fdaaf0bae7..588dcb843b 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs @@ -19,6 +19,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.LiveTv.TunerHosts { @@ -145,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts if (protocol == MediaProtocol.Http) { // Use user-defined user-agent. If there isn't one, make it look like a browser. - httpHeaders["User-Agent"] = string.IsNullOrWhiteSpace(info.UserAgent) ? + httpHeaders[HeaderNames.UserAgent] = string.IsNullOrWhiteSpace(info.UserAgent) ? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.85 Safari/537.36" : info.UserAgent; } diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index f73adc5ff8..82bcf2f043 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Services; using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; namespace Emby.Server.Implementations.SocketSharp { @@ -114,9 +115,9 @@ namespace Emby.Server.Implementations.SocketSharp return form; } - public string Accept => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString(); + public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString(); - public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); + public string Authorization => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Authorization]) ? null : request.Headers[HeaderNames.Authorization].ToString(); protected bool validate_form { get; set; } protected bool checked_form { get; set; } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 61db7b8d47..c25204bf2d 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Images { @@ -634,7 +635,7 @@ namespace MediaBrowser.Api.Images var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); - headers["Vary"] = "Accept"; + headers[HeaderNames.Vary] = HeaderNames.Accept; return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index d44b07256d..8eefbdf2ca 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -29,6 +29,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Library { @@ -827,7 +828,7 @@ namespace MediaBrowser.Api.Library var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); if (!string.IsNullOrWhiteSpace(filename)) { - headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\""; + headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\""; } return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 8fdd726b7b..88ed734569 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -24,6 +24,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.LiveTv { @@ -750,9 +751,10 @@ namespace MediaBrowser.Api.LiveTv throw new FileNotFoundException(); } - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) + }; return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment) { @@ -772,9 +774,10 @@ namespace MediaBrowser.Api.LiveTv var directStreamProvider = liveStreamInfo; - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container) + }; return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment) { diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 3e74d59db7..fc81e532d8 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive { @@ -154,7 +155,7 @@ namespace MediaBrowser.Api.Playback.Progressive var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); // TODO: Don't hardcode this - outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); + outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts"); return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) { @@ -196,9 +197,11 @@ namespace MediaBrowser.Api.Playback.Progressive { if (state.MediaSource.IsInfiniteStream) { - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = contentType + }; - outputHeaders["Content-Type"] = contentType; return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) { @@ -298,16 +301,16 @@ namespace MediaBrowser.Api.Playback.Progressive if (trySupportSeek) { - if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"])) + if (!string.IsNullOrWhiteSpace(Request.QueryString[HeaderNames.Range])) { - options.RequestHeaders["Range"] = Request.QueryString["Range"]; + options.RequestHeaders[HeaderNames.Range] = Request.QueryString[HeaderNames.Range]; } } var response = await HttpClient.GetResponse(options).ConfigureAwait(false); if (trySupportSeek) { - foreach (var name in new[] { "Content-Range", "Accept-Ranges" }) + foreach (var name in new[] { HeaderNames.ContentRange, HeaderNames.AcceptRanges }) { var val = response.Headers[name]; if (!string.IsNullOrWhiteSpace(val)) @@ -318,14 +321,7 @@ namespace MediaBrowser.Api.Playback.Progressive } else { - responseHeaders["Accept-Ranges"] = "none"; - } - - // Seeing cases of -1 here - if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) - { - // TODO - //responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); + responseHeaders[HeaderNames.AcceptRanges] = "none"; } if (isHeadRequest) @@ -338,7 +334,7 @@ namespace MediaBrowser.Api.Playback.Progressive var result = new StaticRemoteStreamWriter(response); - result.Headers["Content-Type"] = response.ContentType; + result.Headers[HeaderNames.ContentType] = response.ContentType; // Add the response headers to the result object foreach (var header in responseHeaders) @@ -362,42 +358,14 @@ namespace MediaBrowser.Api.Playback.Progressive // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; - responseHeaders["Accept-Ranges"] = "none"; + responseHeaders[HeaderNames.AcceptRanges] = "none"; var contentType = state.GetMimeType(outputPath); - // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response - // What we really want to do is hunt that down and remove that - var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; - - if (contentLength.HasValue) - { - responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); - } - // Headers only if (isHeadRequest) { - var streamResult = ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); - - var hasHeaders = streamResult as IHasHeaders; - if (hasHeaders != null) - { - // TODO - //if (contentLength.HasValue) - //{ - // hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - //} - //else - //{ - if (hasHeaders.Headers.ContainsKey("Content-Length")) - { - hasHeaders.Headers.Remove("Content-Length"); - } - //} - } - - return streamResult; + return ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); @@ -416,9 +384,11 @@ namespace MediaBrowser.Api.Playback.Progressive state.Dispose(); } - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = contentType + }; - outputHeaders["Content-Type"] = contentType; // Add the response headers to the result object foreach (var item in responseHeaders) @@ -433,22 +403,5 @@ namespace MediaBrowser.Api.Playback.Progressive transcodingLock.Release(); } } - - /// - /// Gets the length of the estimated content. - /// - /// The state. - /// System.Nullable{System.Int64}. - private long? GetEstimatedContentLength(StreamState state) - { - var totalBitrate = state.TotalOutputBitrate ?? 0; - - if (totalBitrate > 0 && state.RunTimeTicks.HasValue) - { - return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); - } - - return null; - } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index dadac5e03d..bea178517b 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Common.Net { @@ -24,8 +25,8 @@ namespace MediaBrowser.Common.Net /// The accept header. public string AcceptHeader { - get => GetHeaderValue("Accept"); - set => RequestHeaders["Accept"] = value; + get => GetHeaderValue(HeaderNames.Accept); + set => RequestHeaders[HeaderNames.Accept] = value; } /// /// Gets or sets the cancellation token. @@ -45,8 +46,8 @@ namespace MediaBrowser.Common.Net /// The user agent. public string UserAgent { - get => GetHeaderValue("User-Agent"); - set => RequestHeaders["User-Agent"] = value; + get => GetHeaderValue(HeaderNames.UserAgent); + set => RequestHeaders[HeaderNames.UserAgent] = value; } /// From df92df7bd63f9fd19ed195f668b45d52d6bd3abc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:22:41 +0100 Subject: [PATCH 50/59] Remove BOM --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.sln | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 94a7e58cb5..bd670df527 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -1,4 +1,4 @@ - + jellyfin diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5898d8f984..01893f1b55 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors diff --git a/MediaBrowser.sln b/MediaBrowser.sln index b1d3fcdda8..e43a582242 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 From 9a4a01fb0e94d8ffbc00f5932f284f7faeb0ee69 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:32:22 +0100 Subject: [PATCH 51/59] Fix DI in FileWriter.TransmitFile --- .../ApplicationHost.cs | 2 +- .../HttpServer/FileWriter.cs | 30 ++++++++----------- .../HttpServer/HttpResultFactory.cs | 6 ++-- .../SocketSharp/WebSocketSharpResponse.cs | 2 -- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index deb1d3cee0..93147539c6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -741,7 +741,7 @@ namespace Emby.Server.Implementations ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); - HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer); + HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper); serviceCollection.AddSingleton(HttpResultFactory); serviceCollection.AddSingleton(this); diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 303db99801..c4a170eaae 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -15,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer { public class FileWriter : IHttpResult { + private readonly IStreamHelper _streamHelper; private ILogger Logger { get; set; } public IFileSystem FileSystem { get; } @@ -45,13 +46,15 @@ namespace Emby.Server.Implementations.HttpServer public string Path { get; set; } - public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem) + public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper) { if (string.IsNullOrEmpty(contentType)) { throw new ArgumentNullException(nameof(contentType)); } + _streamHelper = streamHelper; + Path = path; Logger = logger; FileSystem = fileSystem; @@ -147,8 +150,7 @@ namespace Emby.Server.Implementations.HttpServer } } - private string[] SkipLogExtensions = new string[] - { + private readonly string[] SkipLogExtensions = { ".js", ".html", ".css" @@ -165,8 +167,10 @@ namespace Emby.Server.Implementations.HttpServer } var path = Path; + var offset = RangeStart; + var count = RangeLength; - if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)) + if (string.IsNullOrWhiteSpace(RangeHeader) || RangeStart <= 0 && RangeEnd >= TotalContentLength - 1) { var extension = System.IO.Path.GetExtension(path); @@ -175,20 +179,15 @@ namespace Emby.Server.Implementations.HttpServer Logger.LogDebug("Transmit file {0}", path); } - //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - // TODO not DI friendly lol - await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); - return; + offset = 0; + count = 0; } - // TODO not DI friendly lol - await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); + + await response.TransmitFile(path, offset, count, FileShare, FileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); } finally { - if (OnComplete != null) - { - OnComplete(); - } + OnComplete?.Invoke(); } } @@ -205,8 +204,5 @@ namespace Emby.Server.Implementations.HttpServer get => (HttpStatusCode)Status; set => Status = (int)value; } - - public string StatusDescription { get; set; } - } } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index f4fe6b611a..4632658626 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -34,14 +34,16 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; + private readonly IStreamHelper _streamHelper; /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer) + public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IStreamHelper streamHelper) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; + _streamHelper = streamHelper; _logger = loggerfactory.CreateLogger("HttpResultFactory"); } @@ -541,7 +543,7 @@ namespace Emby.Server.Implementations.HttpServer if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { - var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem) + var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem, _streamHelper) { OnComplete = options.OnComplete, OnError = options.OnError, diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index 5964107651..a7e3e6c700 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -78,8 +78,6 @@ namespace Emby.Server.Implementations.SocketSharp 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) From 913e80fd5527775ea71d6f9606f49c5494cb7b63 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 20:35:07 +0100 Subject: [PATCH 52/59] Add ProcessWebSocketRequest to IHttpListener --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 ++--- Emby.Server.Implementations/HttpServer/IHttpListener.cs | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 293b28e74b..d1b1b5b242 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -756,11 +756,10 @@ namespace Emby.Server.Implementations.HttpServer public Task ProcessWebSocketRequest(HttpContext context) { - // TODO - return ((WebSocketSharpListener)_socketListener).ProcessWebSocketRequest(context); + return _socketListener.ProcessWebSocketRequest(context); } - //TODO Add Jellyfin Route Path Normalizer + //TODO Add Jellyfin Route Path Normalizer private static string NormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 1ef65d9d73..005656d2c1 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.HttpServer { @@ -30,5 +31,7 @@ namespace Emby.Server.Implementations.HttpServer /// Stops this instance. /// Task Stop(); + + Task ProcessWebSocketRequest(HttpContext ctx); } } From 446f9bf81fa3ac737acda71d57fdc7289c227cef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 20:48:04 +0100 Subject: [PATCH 53/59] Remove more Content-Length references --- .../HttpServer/FileWriter.cs | 4 +-- .../HttpServer/RangeRequestWriter.cs | 1 - .../Services/QueryParamCollection.cs | 26 +++---------------- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c4a170eaae..78e2015b1e 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -99,12 +99,10 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; - // Content-Length is the length of what we're serving, not the original content - var lengthString = RangeLength.ToString(UsCulture); var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; Headers[HeaderNames.ContentRange] = rangeString; - Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString); + Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString); } /// diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index a0bfe5d8f9..449159834a 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,7 +96,6 @@ namespace Emby.Server.Implementations.HttpServer RangeStart = requestedRange.Key; RangeLength = 1 + RangeEnd - RangeStart; - // Content-Length is the length of what we're serving, not the original content Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 4631a3b633..7708db00a8 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -25,16 +25,12 @@ namespace MediaBrowser.Model.Services /// /// Adds a new query parameter. /// - public virtual void Add(string key, string value) + public void Add(string key, string value) { - if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase)) - { - return; - } Add(new NameValuePair(key, value)); } - public virtual void Set(string key, string value) + private void Set(string key, string value) { if (string.IsNullOrEmpty(value)) { @@ -62,7 +58,7 @@ namespace MediaBrowser.Model.Services Add(key, value); } - public string Get(string name) + private string Get(string name) { var stringComparison = GetStringComparison(); @@ -77,7 +73,7 @@ namespace MediaBrowser.Model.Services return null; } - public virtual List GetItems(string name) + private List GetItems(string name) { var stringComparison = GetStringComparison(); @@ -111,20 +107,6 @@ namespace MediaBrowser.Model.Services return list; } - public Dictionary ToDictionary() - { - var stringComparer = GetStringComparer(); - - var headers = new Dictionary(stringComparer); - - foreach (var pair in this) - { - headers[pair.Name] = pair.Value; - } - - return headers; - } - public IEnumerable Keys { get From 394d23a73a530b5ce2609d01575e0ced734aecfe Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:14:03 +0100 Subject: [PATCH 54/59] Review comments --- Emby.Server.Implementations/WebSockets/WebSocketManager.cs | 2 +- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 5db2c8da73..04c73ecea7 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.WebSockets ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); - // All messages are expected to be json + // All messages are expected to be valid JSON objects if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase)) { _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message); diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index fc81e532d8..399ac2be09 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -362,6 +362,7 @@ namespace MediaBrowser.Api.Playback.Progressive var contentType = state.GetMimeType(outputPath); + // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response // Headers only if (isHeadRequest) { From 21c2acc520bcebe74dacc4caf1c244e258a12aec Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:27:05 +0100 Subject: [PATCH 55/59] Remove public Host property --- Emby.Server.Implementations/ApplicationHost.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 93147539c6..71a112dacf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations /// /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - public IWebHost Host { get; set; } + public virtual bool CanLaunchWebBrowser { get @@ -625,7 +625,7 @@ namespace Emby.Server.Implementations contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); } - Host = new WebHostBuilder() + var host = new WebHostBuilder() .UseKestrel(options => { options.ListenAnyIP(HttpPort); @@ -652,10 +652,10 @@ namespace Emby.Server.Implementations }) .Build(); - await Host.StartAsync(); + await host.StartAsync(); } - public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) + private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -665,7 +665,8 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + + private async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { if (context.WebSockets.IsWebSocketRequest) { From bba049c987a632375da5e847512efe7ec4a85a31 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:29:25 +0100 Subject: [PATCH 56/59] Make FileSystem readonly --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 78e2015b1e..0e989994b1 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.HttpServer { private readonly IStreamHelper _streamHelper; private ILogger Logger { get; set; } - public IFileSystem FileSystem { get; } + private readonly IFileSystem _fileSystem; private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } @@ -54,10 +54,10 @@ namespace Emby.Server.Implementations.HttpServer } _streamHelper = streamHelper; + _fileSystem = fileSystem; Path = path; Logger = logger; - FileSystem = fileSystem; RangeHeader = rangeHeader; Headers[HeaderNames.ContentType] = contentType; @@ -181,7 +181,7 @@ namespace Emby.Server.Implementations.HttpServer count = 0; } - await response.TransmitFile(path, offset, count, FileShare, FileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); } finally { From 8c609bc9ce9f548161c3d950706653a525f7e1de Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 19:04:09 +0100 Subject: [PATCH 57/59] Reduce aspnet imports --- Emby.Dlna/ControlRequest.cs | 1 - Emby.Dlna/Emby.Dlna.csproj | 5 +++ Emby.Dlna/PlayTo/PlayToController.cs | 1 - .../MediaBrowser.Common.csproj | 3 +- .../Net/WebSocketConnectEventArgs.cs | 41 ------------------- MediaBrowser.Model/MediaBrowser.Model.csproj | 5 +-- 6 files changed, 9 insertions(+), 47 deletions(-) delete mode 100644 MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index 907d437f85..8c227159c4 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Http; diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 71ded23373..4c07087c53 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -58,4 +58,9 @@ + + + + + diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 8d7d59249a..67d5cfef42 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -21,7 +21,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; namespace Emby.Dlna.PlayTo { diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 715f4fccd3..05b48a2a12 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,6 +13,7 @@ + diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs deleted file mode 100644 index 107e674216..0000000000 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using MediaBrowser.Model.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; - -namespace MediaBrowser.Controller.Net -{ - /// - /// Class WebSocketConnectEventArgs - /// - public class WebSocketConnectingEventArgs : EventArgs - { - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - /// - /// Gets or sets the endpoint. - /// - /// The endpoint. - public string Endpoint { get; set; } - /// - /// Gets or sets the query string. - /// - /// The query string. - public IQueryCollection QueryString { get; set; } - /// - /// Gets or sets a value indicating whether [allow connection]. - /// - /// true if [allow connection]; otherwise, false. - public bool AllowConnection { get; set; } - - public WebSocketConnectingEventArgs() - { - QueryString = new QueryCollection(); - AllowConnection = true; - } - } - -} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 2f1ea13d9b..3de2cca2d2 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,8 +13,7 @@ - - + From e4c5d51860b5e421abac8d453c4655188074501d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 19:04:50 +0100 Subject: [PATCH 58/59] Update MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs Co-Authored-By: cvium --- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 399ac2be09..f43f26b2f3 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback.Progressive // Headers only if (isHeadRequest) { - return ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); + return ResultFactory.GetResult(null, Array.Empty(), contentType, responseHeaders); } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); From dfff68b2f4ca8d3da59508eb4cbe2e751ed76f49 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 19:05:47 +0100 Subject: [PATCH 59/59] Make SkipLogExtensions static --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 0e989994b1..c4d2a70e23 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.HttpServer } } - private readonly string[] SkipLogExtensions = { + private static readonly string[] SkipLogExtensions = { ".js", ".html", ".css"