diff options
| author | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
|---|---|---|
| committer | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
| commit | 48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch) | |
| tree | 8dae77a31670a888d733484cb17dd4077d5444e8 /Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | |
| parent | c32d8656382a0eacb301692e0084377fc433ae9b (diff) | |
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'Emby.Server.Implementations/HttpServer/HttpListenerHost.cs')
| -rw-r--r-- | Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 345 |
1 files changed, 232 insertions, 113 deletions
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 937eb8029..0093258e3 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -12,7 +12,6 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations.HttpServer.SocketSharp; using Emby.Server.Implementations.Services; using MediaBrowser.Common.Net; using MediaBrowser.Common.Security; @@ -25,6 +24,10 @@ using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Text; +using System.Net.Sockets; +using Emby.Server.Implementations.Net; +using MediaBrowser.Common.Events; +using MediaBrowser.Model.Events; namespace Emby.Server.Implementations.HttpServer { @@ -35,64 +38,47 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; public string[] UrlPrefixes { get; private set; } - private readonly List<IService> _restServices = new List<IService>(); - private IHttpListener _listener; - public event EventHandler<WebSocketConnectEventArgs> WebSocketConnected; - public event EventHandler<WebSocketConnectingEventArgs> WebSocketConnecting; + public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; - private readonly IMemoryStreamFactory _memoryStreamProvider; private readonly IServerApplicationHost _appHost; private readonly ITextEncoding _textEncoding; - private readonly ISocketFactory _socketFactory; - private readonly ICryptoProvider _cryptoProvider; - private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly IXmlSerializer _xmlSerializer; - private readonly X509Certificate _certificate; - private readonly IEnvironmentInfo _environment; private readonly Func<Type, Func<string, object>> _funcParseFn; - private readonly bool _enableDualModeSockets; - public Action<IRequest, IResponse, object>[] RequestFilters { get; set; } public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; } private readonly Dictionary<Type, Type> ServiceOperationsMap = new Dictionary<Type, Type>(); public static HttpListenerHost Instance { get; protected set; } + private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>(); + private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>(); + public HttpListenerHost(IServerApplicationHost applicationHost, ILogger logger, IServerConfigurationManager config, - string serviceName, - string defaultRedirectPath, INetworkManager networkManager, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, IEnvironmentInfo environment, X509Certificate certificate, Func<Type, Func<string, object>> funcParseFn, bool enableDualModeSockets, IFileSystem fileSystem) + string defaultRedirectPath, INetworkManager networkManager, ITextEncoding textEncoding, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer, Func<Type, Func<string, object>> funcParseFn) { Instance = this; _appHost = applicationHost; DefaultRedirectPath = defaultRedirectPath; _networkManager = networkManager; - _memoryStreamProvider = memoryStreamProvider; _textEncoding = textEncoding; - _socketFactory = socketFactory; - _cryptoProvider = cryptoProvider; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; - _environment = environment; - _certificate = certificate; - _funcParseFn = funcParseFn; - _enableDualModeSockets = enableDualModeSockets; - _fileSystem = fileSystem; _config = config; _logger = logger; + _funcParseFn = funcParseFn; - RequestFilters = new Action<IRequest, IResponse, object>[] { }; ResponseFilters = new Action<IRequest, IResponse, object>[] { }; } @@ -140,12 +126,6 @@ namespace Emby.Server.Implementations.HttpServer attribute.RequestFilter(req, res, requestDto); } - //Exec global filters - foreach (var requestFilter in RequestFilters) - { - requestFilter(req, res, requestDto); - } - //Exec remaining RequestFilter attributes with Priority >= 0 for (; i < count && attributes[i].Priority >= 0; i++) { @@ -181,45 +161,38 @@ namespace Emby.Server.Implementations.HttpServer return attributes; } - private IHttpListener GetListener() - { - //return new KestrelHost.KestrelListener(_logger, _environment, _fileSystem); - - return new WebSocketSharpListener(_logger, - _certificate, - _memoryStreamProvider, - _textEncoding, - _networkManager, - _socketFactory, - _cryptoProvider, - _enableDualModeSockets, - _fileSystem, - _environment); - } - - private void OnWebSocketConnecting(WebSocketConnectingEventArgs args) + private void OnWebSocketConnected(WebSocketConnectEventArgs e) { if (_disposed) { return; } - if (WebSocketConnecting != null) + var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger, _textEncoding) { - WebSocketConnecting(this, args); - } - } + OnReceive = ProcessWebSocketMessageReceived, + Url = e.Url, + QueryString = e.QueryString ?? new QueryParamCollection() + }; - private void OnWebSocketConnected(WebSocketConnectEventArgs args) - { - if (_disposed) + connection.Closed += Connection_Closed; + + lock (_webSocketConnections) { - return; + _webSocketConnections.Add(connection); } if (WebSocketConnected != null) { - WebSocketConnected(this, args); + EventHelper.FireEventIfNotNull(WebSocketConnected, this, new GenericEventArgs<IWebSocketConnection>(connection), _logger); + } + } + + private void Connection_Closed(object sender, EventArgs e) + { + lock (_webSocketConnections) + { + _webSocketConnections.Remove((IWebSocketConnection)sender); } } @@ -271,16 +244,20 @@ namespace Emby.Server.Implementations.HttpServer return statusCode; } - private void ErrorHandler(Exception ex, IRequest httpReq, bool logException = true) + private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, bool logExceptionMessage) { try { ex = GetActualException(ex); - if (logException) + if (logExceptionStackTrace) { _logger.ErrorException("Error processing request", ex); } + else if (logExceptionMessage) + { + _logger.Error(ex.Message); + } var httpRes = httpReq.Response; @@ -293,7 +270,7 @@ namespace Emby.Server.Implementations.HttpServer httpRes.StatusCode = statusCode; httpRes.ContentType = "text/html"; - Write(httpRes, ex.Message); + await Write(httpRes, NormalizeExceptionMessage(ex.Message)).ConfigureAwait(false); } catch { @@ -301,11 +278,46 @@ namespace Emby.Server.Implementations.HttpServer } } + private string NormalizeExceptionMessage(string msg) + { + if (msg == null) + { + return string.Empty; + } + + // Strip any information we don't want to reveal + + msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase); + msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase); + + return msg; + } + /// <summary> /// Shut down the Web Service /// </summary> public void Stop() { + List<IWebSocketConnection> connections; + + lock (_webSocketConnections) + { + connections = _webSocketConnections.ToList(); + _webSocketConnections.Clear(); + } + + foreach (var connection in connections) + { + try + { + connection.Dispose(); + } + catch + { + + } + } + if (_listener != null) { _logger.Info("Stopping HttpListener..."); @@ -329,9 +341,9 @@ namespace Emby.Server.Implementations.HttpServer { var extension = GetExtension(url); - if (string.IsNullOrWhiteSpace(extension) || !_skipLogExtensions.ContainsKey(extension)) + if (string.IsNullOrEmpty(extension) || !_skipLogExtensions.ContainsKey(extension)) { - if (string.IsNullOrWhiteSpace(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) + if (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) { return true; } @@ -422,12 +434,53 @@ namespace Emby.Server.Implementations.HttpServer return true; } + private bool ValidateRequest(string remoteIp, bool isLocal) + { + if (isLocal) + { + return true; + } + + if (_config.Configuration.EnableRemoteAccess) + { + var addressFilter = _config.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); + + if (addressFilter.Length > 0 && !_networkManager.IsInLocalNetwork(remoteIp)) + { + if (_config.Configuration.IsRemoteIPFilterBlacklist) + { + return !_networkManager.IsAddressInSubnets(remoteIp, addressFilter); + } + else + { + return _networkManager.IsAddressInSubnets(remoteIp, addressFilter); + } + } + } + else + { + if (!_networkManager.IsInLocalNetwork(remoteIp)) + { + return false; + } + } + + return true; + } + private bool ValidateSsl(string remoteIp, string urlString) { - if (_config.Configuration.RequireHttps && _appHost.EnableHttps) + if (_config.Configuration.RequireHttps && _appHost.EnableHttps && !_config.Configuration.IsBehindProxy) { if (urlString.IndexOf("https://", StringComparison.OrdinalIgnoreCase) == -1) { + // These are hacks, but if these ever occur on ipv6 in the local network they could be incorrectly redirected + if (urlString.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) != -1 || + urlString.IndexOf("dlna/", StringComparison.OrdinalIgnoreCase) != -1) + { + return true; + } + if (!_networkManager.IsInLocalNetwork(remoteIp)) { return false; @@ -448,7 +501,7 @@ namespace Emby.Server.Implementations.HttpServer bool enableLog = false; bool logHeaders = false; string urlToLog = null; - string remoteIp = null; + string remoteIp = httpReq.RemoteIp; try { @@ -456,7 +509,7 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 503; httpRes.ContentType = "text/plain"; - Write(httpRes, "Server shutting down"); + await Write(httpRes, "Server shutting down").ConfigureAwait(false); return; } @@ -464,17 +517,21 @@ namespace Emby.Server.Implementations.HttpServer { httpRes.StatusCode = 400; httpRes.ContentType = "text/plain"; - Write(httpRes, "Invalid host"); + await Write(httpRes, "Invalid host").ConfigureAwait(false); return; } - if (!ValidateSsl(httpReq.RemoteIp, urlString)) + if (!ValidateRequest(remoteIp, httpReq.IsLocal)) { - var httpsUrl = urlString - .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) - .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); + httpRes.StatusCode = 403; + httpRes.ContentType = "text/plain"; + await Write(httpRes, "Forbidden").ConfigureAwait(false); + return; + } - RedirectToUrl(httpRes, httpsUrl); + if (!ValidateSsl(httpReq.RemoteIp, urlString)) + { + RedirectToSecureUrl(httpReq, httpRes, urlString); return; } @@ -485,7 +542,7 @@ namespace Emby.Server.Implementations.HttpServer httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization"); httpRes.ContentType = "text/plain"; - Write(httpRes, string.Empty); + await Write(httpRes, string.Empty).ConfigureAwait(false); return; } @@ -498,7 +555,6 @@ namespace Emby.Server.Implementations.HttpServer if (enableLog) { urlToLog = GetUrlToLog(urlString); - remoteIp = httpReq.RemoteIp; LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null); } @@ -527,9 +583,9 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - Write(httpRes, + await Write(httpRes, "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + - newUrl + "\">" + newUrl + "</a></body></html>"); + newUrl + "\">" + newUrl + "</a></body></html>").ConfigureAwait(false); return; } } @@ -544,9 +600,9 @@ namespace Emby.Server.Implementations.HttpServer if (!string.Equals(newUrl, urlString, StringComparison.OrdinalIgnoreCase)) { - Write(httpRes, + await Write(httpRes, "<!doctype html><html><head><title>Emby</title></head><body>Please update your Emby bookmark to <a href=\"" + - newUrl + "\">" + newUrl + "</a></body></html>"); + newUrl + "\">" + newUrl + "</a></body></html>").ConfigureAwait(false); return; } } @@ -572,18 +628,29 @@ namespace Emby.Server.Implementations.HttpServer return; } - if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(httpReq.QueryString["r"], "0", StringComparison.OrdinalIgnoreCase)) { - RedirectToUrl(httpRes, "web/pin.html"); - return; + if (localPath.EndsWith("web/dashboard.html", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, "index.html#!/dashboard.html"); + } + + if (localPath.EndsWith("web/home.html", StringComparison.OrdinalIgnoreCase)) + { + RedirectToUrl(httpRes, "index.html"); + } } - if (!string.IsNullOrWhiteSpace(GlobalResponse)) + if (!string.IsNullOrEmpty(GlobalResponse)) { - httpRes.StatusCode = 503; - httpRes.ContentType = "text/html"; - Write(httpRes, GlobalResponse); - return; + // We don't want the address pings in ApplicationHost to fail + if (localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1) + { + httpRes.StatusCode = 503; + httpRes.ContentType = "text/html"; + await Write(httpRes, GlobalResponse).ConfigureAwait(false); + return; + } } var handler = GetServiceHandler(httpReq); @@ -594,23 +661,34 @@ namespace Emby.Server.Implementations.HttpServer } else { - ErrorHandler(new FileNotFoundException(), httpReq, false); + await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false); } } catch (OperationCanceledException ex) { - ErrorHandler(ex, httpReq, false); + await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); + } + + catch (IOException ex) + { + await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); + } + + catch (SocketException ex) + { + await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false); + } + + catch (SecurityException ex) + { + await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false); } catch (Exception ex) { var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase); -#if DEBUG - logException = true; -#endif - - ErrorHandler(ex, httpReq, logException); + await ErrorHandler(ex, httpReq, logException, false).ConfigureAwait(false); } finally { @@ -655,13 +733,36 @@ namespace Emby.Server.Implementations.HttpServer return null; } - private void Write(IResponse response, string text) + private Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); response.SetContentLength(bOutput.Length); - var outputStream = response.OutputStream; - outputStream.Write(bOutput, 0, bOutput.Length); + return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); + } + + private void RedirectToSecureUrl(IHttpRequest httpReq, IResponse httpRes, string url) + { + int currentPort; + Uri uri; + if (Uri.TryCreate(url, UriKind.Absolute, out uri)) + { + currentPort = uri.Port; + var builder = new UriBuilder(uri); + builder.Port = _config.Configuration.PublicHttpsPort; + builder.Scheme = "https"; + url = builder.Uri.ToString(); + + RedirectToUrl(httpRes, url); + } + else + { + var httpsUrl = url + .Replace("http://", "https://", StringComparison.OrdinalIgnoreCase) + .Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase); + + RedirectToUrl(httpRes, url); + } } public static void RedirectToUrl(IResponse httpRes, string url) @@ -676,26 +777,18 @@ namespace Emby.Server.Implementations.HttpServer /// Adds the rest handlers. /// </summary> /// <param name="services">The services.</param> - public void Init(IEnumerable<IService> services) + public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners) { - _restServices.AddRange(services); + _webSocketListeners = listeners.ToArray(); ServiceController = new ServiceController(); _logger.Info("Calling ServiceStack AppHost.Init"); - var types = _restServices.Select(r => r.GetType()).ToArray(); + var types = services.Select(r => r.GetType()).ToArray(); ServiceController.Init(this, types); - var list = new List<Action<IRequest, IResponse, object>>(); - foreach (var filter in _appHost.GetExports<IRequestFilter>()) - { - list.Add(filter.Filter); - } - - RequestFilters = list.ToArray(); - ResponseFilters = new Action<IRequest, IResponse, object>[] { new ResponseFilter(_logger).FilterResponse @@ -750,12 +843,12 @@ namespace Emby.Server.Implementations.HttpServer _xmlSerializer.SerializeToStream(o, stream); } - public object DeserializeXml(Type type, Stream stream) + public Task<object> DeserializeXml(Type type, Stream stream) { - return _xmlSerializer.DeserializeFromStream(type, stream); + return Task.FromResult(_xmlSerializer.DeserializeFromStream(type, stream)); } - public object DeserializeJson(Type type, Stream stream) + public Task<object> DeserializeJson(Type type, Stream stream) { //using (var reader = new StreamReader(stream)) //{ @@ -763,7 +856,7 @@ namespace Emby.Server.Implementations.HttpServer // Logger.Info(json); // return _jsonSerializer.DeserializeFromString(json, type); //} - return _jsonSerializer.DeserializeFromStream(stream, type); + return _jsonSerializer.DeserializeFromStreamAsync(stream, type); } private string NormalizeEmbyRoutePath(string path) @@ -815,20 +908,46 @@ namespace Emby.Server.Implementations.HttpServer } } + /// <summary> + /// Processes the web socket message received. + /// </summary> + /// <param name="result">The result.</param> + private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) + { + if (_disposed) + { + return Task.CompletedTask; + } + + //_logger.Debug("Websocket message received: {0}", result.MessageType); + + var tasks = _webSocketListeners.Select(i => Task.Run(async () => + { + try + { + await i.ProcessMessage(result).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("{0} failed processing WebSocket message {1}", ex, i.GetType().Name, result.MessageType ?? string.Empty); + } + })); + + return Task.WhenAll(tasks); + } + public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } - public void StartServer(string[] urlPrefixes) + public void StartServer(string[] urlPrefixes, IHttpListener httpListener) { UrlPrefixes = urlPrefixes; - _listener = GetListener(); + _listener = httpListener; _listener.WebSocketConnected = OnWebSocketConnected; - _listener.WebSocketConnecting = OnWebSocketConnecting; _listener.ErrorHandler = ErrorHandler; _listener.RequestHandler = RequestHandler; |
