mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-10-31 10:37:22 -04:00 
			
		
		
		
	Kill HttpListenerHost
This commit is contained in:
		
							parent
							
								
									6ff372a550
								
							
						
					
					
						commit
						571d0570f5
					
				| @ -96,12 +96,12 @@ using MediaBrowser.Providers.Manager; | ||||
| using MediaBrowser.Providers.Plugins.TheTvdb; | ||||
| using MediaBrowser.Providers.Subtitles; | ||||
| using MediaBrowser.XbmcMetadata.Providers; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.AspNetCore.Mvc; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Prometheus.DotNetRuntime; | ||||
| using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; | ||||
| using WebSocketManager = Emby.Server.Implementations.HttpServer.WebSocketManager; | ||||
| 
 | ||||
| namespace Emby.Server.Implementations | ||||
| { | ||||
| @ -122,9 +122,11 @@ namespace Emby.Server.Implementations | ||||
| 
 | ||||
|         private IMediaEncoder _mediaEncoder; | ||||
|         private ISessionManager _sessionManager; | ||||
|         private IHttpServer _httpServer; | ||||
|         private IWebSocketManager _webSocketManager; | ||||
|         private IHttpClient _httpClient; | ||||
| 
 | ||||
|         private string[] _urlPrefixes; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Gets a value indicating whether this instance can self restart. | ||||
|         /// </summary> | ||||
| @ -444,7 +446,6 @@ namespace Emby.Server.Implementations | ||||
|             Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); | ||||
| 
 | ||||
|             Logger.LogInformation("Core startup complete"); | ||||
|             _httpServer.GlobalResponse = null; | ||||
| 
 | ||||
|             stopWatch.Restart(); | ||||
|             await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); | ||||
| @ -500,9 +501,6 @@ namespace Emby.Server.Implementations | ||||
|             RegisterServices(); | ||||
|         } | ||||
| 
 | ||||
|         public Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next) | ||||
|             => _httpServer.RequestHandler(context, next); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Registers services/resources with the service collection that will be available via DI. | ||||
|         /// </summary> | ||||
| @ -577,7 +575,7 @@ namespace Emby.Server.Implementations | ||||
| 
 | ||||
|             ServiceCollection.AddSingleton<ISearchEngine, SearchEngine>(); | ||||
| 
 | ||||
|             ServiceCollection.AddSingleton<IHttpServer, HttpListenerHost>(); | ||||
|             ServiceCollection.AddSingleton<IWebSocketManager, WebSocketManager>(); | ||||
| 
 | ||||
|             ServiceCollection.AddSingleton<IImageProcessor, ImageProcessor>(); | ||||
| 
 | ||||
| @ -650,7 +648,7 @@ namespace Emby.Server.Implementations | ||||
| 
 | ||||
|             _mediaEncoder = Resolve<IMediaEncoder>(); | ||||
|             _sessionManager = Resolve<ISessionManager>(); | ||||
|             _httpServer = Resolve<IHttpServer>(); | ||||
|             _webSocketManager = Resolve<IWebSocketManager>(); | ||||
|             _httpClient = Resolve<IHttpClient>(); | ||||
| 
 | ||||
|             ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); | ||||
| @ -771,7 +769,8 @@ namespace Emby.Server.Implementations | ||||
|                         .Where(i => i != null) | ||||
|                         .ToArray(); | ||||
| 
 | ||||
|             _httpServer.Init(GetExports<IWebSocketListener>(), GetUrlPrefixes()); | ||||
|             _urlPrefixes = GetUrlPrefixes().ToArray(); | ||||
|             _webSocketManager.Init(GetExports<IWebSocketListener>()); | ||||
| 
 | ||||
|             Resolve<ILibraryManager>().AddParts( | ||||
|                 GetExports<IResolverIgnoreRule>(), | ||||
| @ -937,7 +936,7 @@ namespace Emby.Server.Implementations | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (!_httpServer.UrlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) | ||||
|             if (!_urlPrefixes.SequenceEqual(GetUrlPrefixes(), StringComparer.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 requiresRestart = true; | ||||
|             } | ||||
|  | ||||
| @ -15,7 +15,7 @@ namespace Emby.Server.Implementations | ||||
|         public static Dictionary<string, string> DefaultConfiguration => new Dictionary<string, string> | ||||
|         { | ||||
|             { HostWebClientKey, bool.TrueString }, | ||||
|             { HttpListenerHost.DefaultRedirectKey, "web/index.html" }, | ||||
|             { DefaultRedirectKey, "web/index.html" }, | ||||
|             { FfmpegProbeSizeKey, "1G" }, | ||||
|             { FfmpegAnalyzeDurationKey, "200M" }, | ||||
|             { PlaylistsAllowDuplicatesKey, bool.TrueString }, | ||||
|  | ||||
| @ -1,315 +0,0 @@ | ||||
| #pragma warning disable CS1591 | ||||
| 
 | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Net.WebSockets; | ||||
| using System.Threading.Tasks; | ||||
| using Jellyfin.Data.Events; | ||||
| using MediaBrowser.Common.Extensions; | ||||
| using MediaBrowser.Common.Net; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using MediaBrowser.Controller.Net; | ||||
| using MediaBrowser.Model.Globalization; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Microsoft.Extensions.Primitives; | ||||
| 
 | ||||
| namespace Emby.Server.Implementations.HttpServer | ||||
| { | ||||
|     public class HttpListenerHost : IHttpServer | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// The key for a setting that specifies the default redirect path | ||||
|         /// to use for requests where the URL base prefix is invalid or missing. | ||||
|         /// </summary> | ||||
|         public const string DefaultRedirectKey = "HttpListenerHost:DefaultRedirectPath"; | ||||
| 
 | ||||
|         private readonly ILogger<HttpListenerHost> _logger; | ||||
|         private readonly ILoggerFactory _loggerFactory; | ||||
|         private readonly IServerConfigurationManager _config; | ||||
|         private readonly INetworkManager _networkManager; | ||||
|         private readonly string _defaultRedirectPath; | ||||
|         private readonly string _baseUrlPrefix; | ||||
| 
 | ||||
|         private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>(); | ||||
|         private bool _disposed = false; | ||||
| 
 | ||||
|         public HttpListenerHost( | ||||
|             ILogger<HttpListenerHost> logger, | ||||
|             IServerConfigurationManager config, | ||||
|             IConfiguration configuration, | ||||
|             INetworkManager networkManager, | ||||
|             ILocalizationManager localizationManager, | ||||
|             ILoggerFactory loggerFactory) | ||||
|         { | ||||
|             _logger = logger; | ||||
|             _config = config; | ||||
|             _defaultRedirectPath = configuration[DefaultRedirectKey]; | ||||
|             _baseUrlPrefix = _config.Configuration.BaseUrl; | ||||
|             _networkManager = networkManager; | ||||
|             _loggerFactory = loggerFactory; | ||||
| 
 | ||||
|             Instance = this; | ||||
|             GlobalResponse = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); | ||||
|         } | ||||
| 
 | ||||
|         public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; | ||||
| 
 | ||||
|         public static HttpListenerHost Instance { get; protected set; } | ||||
| 
 | ||||
|         public string[] UrlPrefixes { get; private set; } | ||||
| 
 | ||||
|         public string GlobalResponse { get; set; } | ||||
| 
 | ||||
|         private static string NormalizeConfiguredLocalAddress(string address) | ||||
|         { | ||||
|             var add = address.AsSpan().Trim('/'); | ||||
|             int index = add.IndexOf('/'); | ||||
|             if (index != -1) | ||||
|             { | ||||
|                 add = add.Slice(index + 1); | ||||
|             } | ||||
| 
 | ||||
|             return add.TrimStart('/').ToString(); | ||||
|         } | ||||
| 
 | ||||
|         private bool ValidateHost(string host) | ||||
|         { | ||||
|             var hosts = _config | ||||
|                 .Configuration | ||||
|                 .LocalNetworkAddresses | ||||
|                 .Select(NormalizeConfiguredLocalAddress) | ||||
|                 .ToList(); | ||||
| 
 | ||||
|             if (hosts.Count == 0) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|             host ??= string.Empty; | ||||
| 
 | ||||
|             if (_networkManager.IsInPrivateAddressSpace(host)) | ||||
|             { | ||||
|                 hosts.Add("localhost"); | ||||
|                 hosts.Add("127.0.0.1"); | ||||
| 
 | ||||
|                 return hosts.Any(i => host.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1); | ||||
|             } | ||||
| 
 | ||||
|             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; | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         public Task RequestHandler(HttpContext context, Func<Task> next) | ||||
|         { | ||||
|             if (context.WebSockets.IsWebSocketRequest) | ||||
|             { | ||||
|                 return WebSocketRequestHandler(context); | ||||
|             } | ||||
| 
 | ||||
|             return HttpRequestHandler(context, next); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Overridable method that can be used to implement a custom handler. | ||||
|         /// </summary> | ||||
|         private async Task HttpRequestHandler(HttpContext httpContext, Func<Task> next) | ||||
|         { | ||||
|             var cancellationToken = httpContext.RequestAborted; | ||||
|             var httpRes = httpContext.Response; | ||||
|             var host = httpContext.Request.Host.ToString(); | ||||
|             var localPath = httpContext.Request.Path.ToString(); | ||||
|             string remoteIp = httpContext.Request.RemoteIp(); | ||||
| 
 | ||||
|             if (_disposed) | ||||
|             { | ||||
|                 httpRes.StatusCode = 503; | ||||
|                 httpRes.ContentType = "text/plain"; | ||||
|                 await httpRes.WriteAsync("Server shutting down", cancellationToken).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (!ValidateHost(host)) | ||||
|             { | ||||
|                 httpRes.StatusCode = 400; | ||||
|                 httpRes.ContentType = "text/plain"; | ||||
|                 await httpRes.WriteAsync("Invalid host", cancellationToken).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (!ValidateRequest(remoteIp, httpContext.Request.IsLocal())) | ||||
|             { | ||||
|                 httpRes.StatusCode = 403; | ||||
|                 httpRes.ContentType = "text/plain"; | ||||
|                 await httpRes.WriteAsync("Forbidden", cancellationToken).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (string.Equals(httpContext.Request.Method, "OPTIONS", StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 httpRes.StatusCode = 200; | ||||
|                 foreach (var (key, value) in GetDefaultCorsHeaders(httpContext)) | ||||
|                 { | ||||
|                     httpRes.Headers.Add(key, value); | ||||
|                 } | ||||
| 
 | ||||
|                 httpRes.ContentType = "text/plain"; | ||||
|                 await httpRes.WriteAsync(string.Empty, cancellationToken).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) | ||||
|                 || string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase) | ||||
|                 || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) | ||||
|                 || string.IsNullOrEmpty(localPath) | ||||
|                 || !localPath.StartsWith(_baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 // Always redirect back to the default path if the base prefix is invalid or missing | ||||
|                 _logger.LogDebug("Normalizing a URL at {0}", localPath); | ||||
|                 httpRes.Redirect(_baseUrlPrefix + "/" + _defaultRedirectPath); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             if (!string.IsNullOrEmpty(GlobalResponse)) | ||||
|             { | ||||
|                 // 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 httpRes.WriteAsync(GlobalResponse, cancellationToken).ConfigureAwait(false); | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             await next().ConfigureAwait(false); | ||||
|         } | ||||
| 
 | ||||
|         private async Task WebSocketRequestHandler(HttpContext context) | ||||
|         { | ||||
|             if (_disposed) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); | ||||
| 
 | ||||
|                 WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); | ||||
| 
 | ||||
|                 using var connection = new WebSocketConnection( | ||||
|                     _loggerFactory.CreateLogger<WebSocketConnection>(), | ||||
|                     webSocket, | ||||
|                     context.Connection.RemoteIpAddress, | ||||
|                     context.Request.Query) | ||||
|                 { | ||||
|                     OnReceive = ProcessWebSocketMessageReceived | ||||
|                 }; | ||||
| 
 | ||||
|                 WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); | ||||
| 
 | ||||
|                 await connection.ProcessAsync().ConfigureAwait(false); | ||||
|                 _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); | ||||
|             } | ||||
|             catch (Exception ex) // Otherwise ASP.Net will ignore the exception | ||||
|             { | ||||
|                 _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); | ||||
|                 if (!context.Response.HasStarted) | ||||
|                 { | ||||
|                     context.Response.StatusCode = 500; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         public IDictionary<string, string> GetDefaultCorsHeaders(HttpContext httpContext) | ||||
|         { | ||||
|             var origin = httpContext.Request.Headers["Origin"]; | ||||
|             if (origin == StringValues.Empty) | ||||
|             { | ||||
|                 origin = httpContext.Request.Headers["Host"]; | ||||
|                 if (origin == StringValues.Empty) | ||||
|                 { | ||||
|                     origin = "*"; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             var headers = new Dictionary<string, string>(); | ||||
|             headers.Add("Access-Control-Allow-Origin", origin); | ||||
|             headers.Add("Access-Control-Allow-Credentials", "true"); | ||||
|             headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); | ||||
|             headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie"); | ||||
|             return headers; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds the rest handlers. | ||||
|         /// </summary> | ||||
|         /// <param name="listeners">The web socket listeners.</param> | ||||
|         /// <param name="urlPrefixes">The URL prefixes. See <see cref="UrlPrefixes"/>.</param> | ||||
|         public void Init(IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes) | ||||
|         { | ||||
|             _webSocketListeners = listeners.ToArray(); | ||||
|             UrlPrefixes = urlPrefixes.ToArray(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Processes the web socket message received. | ||||
|         /// </summary> | ||||
|         /// <param name="result">The result.</param> | ||||
|         private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) | ||||
|         { | ||||
|             if (_disposed) | ||||
|             { | ||||
|                 return Task.CompletedTask; | ||||
|             } | ||||
| 
 | ||||
|             IEnumerable<Task> GetTasks() | ||||
|             { | ||||
|                 foreach (var x in _webSocketListeners) | ||||
|                 { | ||||
|                     yield return x.ProcessMessageAsync(result); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return Task.WhenAll(GetTasks()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										102
									
								
								Emby.Server.Implementations/HttpServer/WebSocketManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								Emby.Server.Implementations/HttpServer/WebSocketManager.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| #pragma warning disable CS1591 | ||||
| 
 | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Net.WebSockets; | ||||
| using System.Threading.Tasks; | ||||
| using Jellyfin.Data.Events; | ||||
| using MediaBrowser.Controller.Net; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Logging; | ||||
| 
 | ||||
| namespace Emby.Server.Implementations.HttpServer | ||||
| { | ||||
|     public class WebSocketManager : IWebSocketManager | ||||
|     { | ||||
|         private readonly ILogger<WebSocketManager> _logger; | ||||
|         private readonly ILoggerFactory _loggerFactory; | ||||
| 
 | ||||
|         private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>(); | ||||
|         private bool _disposed = false; | ||||
| 
 | ||||
|         public WebSocketManager( | ||||
|             ILogger<WebSocketManager> logger, | ||||
|             ILoggerFactory loggerFactory) | ||||
|         { | ||||
|             _logger = logger; | ||||
|             _loggerFactory = loggerFactory; | ||||
|         } | ||||
| 
 | ||||
|         public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         public async Task WebSocketRequestHandler(HttpContext context) | ||||
|         { | ||||
|             if (_disposed) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             try | ||||
|             { | ||||
|                 _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); | ||||
| 
 | ||||
|                 WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); | ||||
| 
 | ||||
|                 using var connection = new WebSocketConnection( | ||||
|                     _loggerFactory.CreateLogger<WebSocketConnection>(), | ||||
|                     webSocket, | ||||
|                     context.Connection.RemoteIpAddress, | ||||
|                     context.Request.Query) | ||||
|                 { | ||||
|                     OnReceive = ProcessWebSocketMessageReceived | ||||
|                 }; | ||||
| 
 | ||||
|                 WebSocketConnected?.Invoke(this, new GenericEventArgs<IWebSocketConnection>(connection)); | ||||
| 
 | ||||
|                 await connection.ProcessAsync().ConfigureAwait(false); | ||||
|                 _logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress); | ||||
|             } | ||||
|             catch (Exception ex) // Otherwise ASP.Net will ignore the exception | ||||
|             { | ||||
|                 _logger.LogError(ex, "WS {IP} WebSocketRequestHandler error", context.Connection.RemoteIpAddress); | ||||
|                 if (!context.Response.HasStarted) | ||||
|                 { | ||||
|                     context.Response.StatusCode = 500; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds the rest handlers. | ||||
|         /// </summary> | ||||
|         /// <param name="listeners">The web socket listeners.</param> | ||||
|         public void Init(IEnumerable<IWebSocketListener> listeners) | ||||
|         { | ||||
|             _webSocketListeners = listeners.ToArray(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Processes the web socket message received. | ||||
|         /// </summary> | ||||
|         /// <param name="result">The result.</param> | ||||
|         private Task ProcessWebSocketMessageReceived(WebSocketMessageInfo result) | ||||
|         { | ||||
|             if (_disposed) | ||||
|             { | ||||
|                 return Task.CompletedTask; | ||||
|             } | ||||
| 
 | ||||
|             IEnumerable<Task> GetTasks() | ||||
|             { | ||||
|                 foreach (var x in _webSocketListeners) | ||||
|                 { | ||||
|                     yield return x.ProcessMessageAsync(result); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             return Task.WhenAll(GetTasks()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Session | ||||
|         private readonly ILogger<SessionWebSocketListener> _logger; | ||||
|         private readonly ILoggerFactory _loggerFactory; | ||||
| 
 | ||||
|         private readonly IHttpServer _httpServer; | ||||
|         private readonly IWebSocketManager _webSocketManager; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// The KeepAlive cancellation token. | ||||
| @ -72,19 +72,19 @@ namespace Emby.Server.Implementations.Session | ||||
|         /// <param name="logger">The logger.</param> | ||||
|         /// <param name="sessionManager">The session manager.</param> | ||||
|         /// <param name="loggerFactory">The logger factory.</param> | ||||
|         /// <param name="httpServer">The HTTP server.</param> | ||||
|         /// <param name="webSocketManager">The HTTP server.</param> | ||||
|         public SessionWebSocketListener( | ||||
|             ILogger<SessionWebSocketListener> logger, | ||||
|             ISessionManager sessionManager, | ||||
|             ILoggerFactory loggerFactory, | ||||
|             IHttpServer httpServer) | ||||
|             IWebSocketManager webSocketManager) | ||||
|         { | ||||
|             _logger = logger; | ||||
|             _sessionManager = sessionManager; | ||||
|             _loggerFactory = loggerFactory; | ||||
|             _httpServer = httpServer; | ||||
|             _webSocketManager = webSocketManager; | ||||
| 
 | ||||
|             httpServer.WebSocketConnected += OnServerManagerWebSocketConnected; | ||||
|             webSocketManager.WebSocketConnected += OnServerManagerWebSocketConnected; | ||||
|         } | ||||
| 
 | ||||
|         private async void OnServerManagerWebSocketConnected(object sender, GenericEventArgs<IWebSocketConnection> e) | ||||
| @ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Session | ||||
|         /// <inheritdoc /> | ||||
|         public void Dispose() | ||||
|         { | ||||
|             _httpServer.WebSocketConnected -= OnServerManagerWebSocketConnected; | ||||
|             _webSocketManager.WebSocketConnected -= OnServerManagerWebSocketConnected; | ||||
|             StopKeepAlive(); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -1,3 +1,4 @@ | ||||
| using Jellyfin.Server.Middleware; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| 
 | ||||
| @ -46,5 +47,65 @@ namespace Jellyfin.Server.Extensions | ||||
|                     c.RoutePrefix = $"{baseUrl}api-docs/redoc"; | ||||
|                 }); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds IP based access validation to the application pipeline. | ||||
|         /// </summary> | ||||
|         /// <param name="appBuilder">The application builder.</param> | ||||
|         /// <returns>The updated application builder.</returns> | ||||
|         public static IApplicationBuilder UseIpBasedAccessValidation(this IApplicationBuilder appBuilder) | ||||
|         { | ||||
|             return appBuilder.UseMiddleware<IpBasedAccessValidationMiddleware>(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds LAN based access filtering to the application pipeline. | ||||
|         /// </summary> | ||||
|         /// <param name="appBuilder">The application builder.</param> | ||||
|         /// <returns>The updated application builder.</returns> | ||||
|         public static IApplicationBuilder UseLanFiltering(this IApplicationBuilder appBuilder) | ||||
|         { | ||||
|             return appBuilder.UseMiddleware<LanFilteringMiddleware>(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds CORS OPTIONS request handling to the application pipeline. | ||||
|         /// </summary> | ||||
|         /// <param name="appBuilder">The application builder.</param> | ||||
|         /// <returns>The updated application builder.</returns> | ||||
|         public static IApplicationBuilder UseCorsOptionsResponse(this IApplicationBuilder appBuilder) | ||||
|         { | ||||
|             return appBuilder.UseMiddleware<CorsOptionsResponseMiddleware>(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds base url redirection to the application pipeline. | ||||
|         /// </summary> | ||||
|         /// <param name="appBuilder">The application builder.</param> | ||||
|         /// <returns>The updated application builder.</returns> | ||||
|         public static IApplicationBuilder UseBaseUrlRedirection(this IApplicationBuilder appBuilder) | ||||
|         { | ||||
|             return appBuilder.UseMiddleware<BaseUrlRedirectionMiddleware>(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds a custom message during server startup to the application pipeline. | ||||
|         /// </summary> | ||||
|         /// <param name="appBuilder">The application builder.</param> | ||||
|         /// <returns>The updated application builder.</returns> | ||||
|         public static IApplicationBuilder UseServerStartupMessage(this IApplicationBuilder appBuilder) | ||||
|         { | ||||
|             return appBuilder.UseMiddleware<ServerStartupMessageMiddleware>(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Adds a WebSocket request handler to the application pipeline. | ||||
|         /// </summary> | ||||
|         /// <param name="appBuilder">The application builder.</param> | ||||
|         /// <returns>The updated application builder.</returns> | ||||
|         public static IApplicationBuilder UseWebSocketHandler(this IApplicationBuilder appBuilder) | ||||
|         { | ||||
|             return appBuilder.UseMiddleware<WebSocketHandlerMiddleware>(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										62
									
								
								Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,62 @@ | ||||
| using System; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Configuration; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; | ||||
| 
 | ||||
| namespace Jellyfin.Server.Middleware | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Redirect requests without baseurl prefix to the baseurl prefixed URL. | ||||
|     /// </summary> | ||||
|     public class BaseUrlRedirectionMiddleware | ||||
|     { | ||||
|         private readonly RequestDelegate _next; | ||||
|         private readonly ILogger<BaseUrlRedirectionMiddleware> _logger; | ||||
|         private readonly IConfiguration _configuration; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="BaseUrlRedirectionMiddleware"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="next">The next delegate in the pipeline.</param> | ||||
|         /// <param name="logger">The logger.</param> | ||||
|         /// <param name="configuration">The application configuration.</param> | ||||
|         public BaseUrlRedirectionMiddleware( | ||||
|             RequestDelegate next, | ||||
|             ILogger<BaseUrlRedirectionMiddleware> logger, | ||||
|             IConfiguration configuration) | ||||
|         { | ||||
|             _next = next; | ||||
|             _logger = logger; | ||||
|             _configuration = configuration; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Executes the middleware action. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The current HTTP context.</param> | ||||
|         /// <param name="serverConfigurationManager">The server configuration manager.</param> | ||||
|         /// <returns>The async task.</returns> | ||||
|         public async Task Invoke(HttpContext httpContext, IServerConfigurationManager serverConfigurationManager) | ||||
|         { | ||||
|             var localPath = httpContext.Request.Path.ToString(); | ||||
|             var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl; | ||||
| 
 | ||||
|             if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) | ||||
|                 || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) | ||||
|                 || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase) | ||||
|                 || string.IsNullOrEmpty(localPath) | ||||
|                 || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 // Always redirect back to the default path if the base prefix is invalid or missing | ||||
|                 _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); | ||||
|                 httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await _next(httpContext).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										69
									
								
								Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								Jellyfin.Server/Middleware/CorsOptionsResponseMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Net.Mime; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| using Microsoft.Extensions.Primitives; | ||||
| 
 | ||||
| namespace Jellyfin.Server.Middleware | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Middleware for handling OPTIONS requests. | ||||
|     /// </summary> | ||||
|     public class CorsOptionsResponseMiddleware | ||||
|     { | ||||
|         private readonly RequestDelegate _next; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="CorsOptionsResponseMiddleware"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="next">The next delegate in the pipeline.</param> | ||||
|         public CorsOptionsResponseMiddleware(RequestDelegate next) | ||||
|         { | ||||
|             _next = next; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Executes the middleware action. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The current HTTP context.</param> | ||||
|         /// <returns>The async task.</returns> | ||||
|         public async Task Invoke(HttpContext httpContext) | ||||
|         { | ||||
|             if (string.Equals(httpContext.Request.Method, HttpMethods.Options, StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 httpContext.Response.StatusCode = 200; | ||||
|                 foreach (var (key, value) in GetDefaultCorsHeaders(httpContext)) | ||||
|                 { | ||||
|                     httpContext.Response.Headers.Add(key, value); | ||||
|                 } | ||||
| 
 | ||||
|                 httpContext.Response.ContentType = MediaTypeNames.Text.Plain; | ||||
|                 await httpContext.Response.WriteAsync(string.Empty, httpContext.RequestAborted).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await _next(httpContext).ConfigureAwait(false); | ||||
|         } | ||||
| 
 | ||||
|         private static IDictionary<string, string> GetDefaultCorsHeaders(HttpContext httpContext) | ||||
|         { | ||||
|             var origin = httpContext.Request.Headers["Origin"]; | ||||
|             if (origin == StringValues.Empty) | ||||
|             { | ||||
|                 origin = httpContext.Request.Headers["Host"]; | ||||
|                 if (origin == StringValues.Empty) | ||||
|                 { | ||||
|                     origin = "*"; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             var headers = new Dictionary<string, string>(); | ||||
|             headers.Add("Access-Control-Allow-Origin", origin); | ||||
|             headers.Add("Access-Control-Allow-Credentials", "true"); | ||||
|             headers.Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS"); | ||||
|             headers.Add("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization, Cookie"); | ||||
|             return headers; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,76 @@ | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Common.Extensions; | ||||
| using MediaBrowser.Common.Net; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| namespace Jellyfin.Server.Middleware | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Validates the IP of requests coming from local networks wrt. remote access. | ||||
|     /// </summary> | ||||
|     public class IpBasedAccessValidationMiddleware | ||||
|     { | ||||
|         private readonly RequestDelegate _next; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="IpBasedAccessValidationMiddleware"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="next">The next delegate in the pipeline.</param> | ||||
|         public IpBasedAccessValidationMiddleware(RequestDelegate next) | ||||
|         { | ||||
|             _next = next; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Executes the middleware action. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The current HTTP context.</param> | ||||
|         /// <param name="networkManager">The network manager.</param> | ||||
|         /// <param name="serverConfigurationManager">The server configuration manager.</param> | ||||
|         /// <returns>The async task.</returns> | ||||
|         public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) | ||||
|         { | ||||
|             if (httpContext.Request.IsLocal()) | ||||
|             { | ||||
|                 await _next(httpContext).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             var remoteIp = httpContext.Request.RemoteIp(); | ||||
| 
 | ||||
|             if (serverConfigurationManager.Configuration.EnableRemoteAccess) | ||||
|             { | ||||
|                 var addressFilter = serverConfigurationManager.Configuration.RemoteIPFilter.Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); | ||||
| 
 | ||||
|                 if (addressFilter.Length > 0 && !networkManager.IsInLocalNetwork(remoteIp)) | ||||
|                 { | ||||
|                     if (serverConfigurationManager.Configuration.IsRemoteIPFilterBlacklist) | ||||
|                     { | ||||
|                         if (networkManager.IsAddressInSubnets(remoteIp, addressFilter)) | ||||
|                         { | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         if (!networkManager.IsAddressInSubnets(remoteIp, addressFilter)) | ||||
|                         { | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 if (!networkManager.IsInLocalNetwork(remoteIp)) | ||||
|                 { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             await _next(httpContext).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										76
									
								
								Jellyfin.Server/Middleware/LanFilteringMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								Jellyfin.Server/Middleware/LanFilteringMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,76 @@ | ||||
| using System; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Common.Net; | ||||
| using MediaBrowser.Controller.Configuration; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| namespace Jellyfin.Server.Middleware | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Validates the LAN host IP based on application configuration. | ||||
|     /// </summary> | ||||
|     public class LanFilteringMiddleware | ||||
|     { | ||||
|         private readonly RequestDelegate _next; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="LanFilteringMiddleware"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="next">The next delegate in the pipeline.</param> | ||||
|         public LanFilteringMiddleware(RequestDelegate next) | ||||
|         { | ||||
|             _next = next; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Executes the middleware action. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The current HTTP context.</param> | ||||
|         /// <param name="networkManager">The network manager.</param> | ||||
|         /// <param name="serverConfigurationManager">The server configuration manager.</param> | ||||
|         /// <returns>The async task.</returns> | ||||
|         public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) | ||||
|         { | ||||
|             var currentHost = httpContext.Request.Host.ToString(); | ||||
|             var hosts = serverConfigurationManager | ||||
|                 .Configuration | ||||
|                 .LocalNetworkAddresses | ||||
|                 .Select(NormalizeConfiguredLocalAddress) | ||||
|                 .ToList(); | ||||
| 
 | ||||
|             if (hosts.Count == 0) | ||||
|             { | ||||
|                 await _next(httpContext).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             currentHost ??= string.Empty; | ||||
| 
 | ||||
|             if (networkManager.IsInPrivateAddressSpace(currentHost)) | ||||
|             { | ||||
|                 hosts.Add("localhost"); | ||||
|                 hosts.Add("127.0.0.1"); | ||||
| 
 | ||||
|                 if (hosts.All(i => currentHost.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1)) | ||||
|                 { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             await _next(httpContext).ConfigureAwait(false); | ||||
|         } | ||||
| 
 | ||||
|         private static string NormalizeConfiguredLocalAddress(string address) | ||||
|         { | ||||
|             var add = address.AsSpan().Trim('/'); | ||||
|             int index = add.IndexOf('/'); | ||||
|             if (index != -1) | ||||
|             { | ||||
|                 add = add.Slice(index + 1); | ||||
|             } | ||||
| 
 | ||||
|             return add.TrimStart('/').ToString(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										38
									
								
								Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,38 @@ | ||||
| using System.Net.Mime; | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Model.Globalization; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| namespace Jellyfin.Server.Middleware | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Shows a custom message during server startup. | ||||
|     /// </summary> | ||||
|     public class ServerStartupMessageMiddleware | ||||
|     { | ||||
|         private readonly RequestDelegate _next; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="ServerStartupMessageMiddleware"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="next">The next delegate in the pipeline.</param> | ||||
|         public ServerStartupMessageMiddleware(RequestDelegate next) | ||||
|         { | ||||
|             _next = next; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Executes the middleware action. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The current HTTP context.</param> | ||||
|         /// <param name="localizationManager">The localization manager.</param> | ||||
|         /// <returns>The async task.</returns> | ||||
|         public async Task Invoke(HttpContext httpContext, ILocalizationManager localizationManager) | ||||
|         { | ||||
|             var message = localizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); | ||||
|             httpContext.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; | ||||
|             httpContext.Response.ContentType = MediaTypeNames.Text.Html; | ||||
|             await httpContext.Response.WriteAsync(message, httpContext.RequestAborted).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										40
									
								
								Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | ||||
| using System.Threading.Tasks; | ||||
| using MediaBrowser.Controller.Net; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| namespace Jellyfin.Server.Middleware | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Handles WebSocket requests. | ||||
|     /// </summary> | ||||
|     public class WebSocketHandlerMiddleware | ||||
|     { | ||||
|         private readonly RequestDelegate _next; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="WebSocketHandlerMiddleware"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="next">The next delegate in the pipeline.</param> | ||||
|         public WebSocketHandlerMiddleware(RequestDelegate next) | ||||
|         { | ||||
|             _next = next; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Executes the middleware action. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The current HTTP context.</param> | ||||
|         /// <param name="webSocketManager">The WebSocket connection manager.</param> | ||||
|         /// <returns>The async task.</returns> | ||||
|         public async Task Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) | ||||
|         { | ||||
|             if (!httpContext.WebSockets.IsWebSocketRequest) | ||||
|             { | ||||
|                 await _next(httpContext).ConfigureAwait(false); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             await webSocketManager.WebSocketRequestHandler(httpContext).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -11,7 +11,6 @@ using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using CommandLine; | ||||
| using Emby.Server.Implementations; | ||||
| using Emby.Server.Implementations.HttpServer; | ||||
| using Emby.Server.Implementations.IO; | ||||
| using Emby.Server.Implementations.Networking; | ||||
| using Jellyfin.Api.Controllers; | ||||
| @ -28,6 +27,7 @@ using Microsoft.Extensions.Logging.Abstractions; | ||||
| using Serilog; | ||||
| using Serilog.Extensions.Logging; | ||||
| using SQLitePCL; | ||||
| using ConfigurationExtensions = MediaBrowser.Controller.Extensions.ConfigurationExtensions; | ||||
| using ILogger = Microsoft.Extensions.Logging.ILogger; | ||||
| 
 | ||||
| namespace Jellyfin.Server | ||||
| @ -594,7 +594,7 @@ namespace Jellyfin.Server | ||||
|             var inMemoryDefaultConfig = ConfigurationOptions.DefaultConfiguration; | ||||
|             if (startupConfig != null && !startupConfig.HostWebClient()) | ||||
|             { | ||||
|                 inMemoryDefaultConfig[HttpListenerHost.DefaultRedirectKey] = "api-docs/swagger"; | ||||
|                 inMemoryDefaultConfig[ConfigurationExtensions.DefaultRedirectKey] = "api-docs/swagger"; | ||||
|             } | ||||
| 
 | ||||
|             return config | ||||
|  | ||||
| @ -84,11 +84,9 @@ namespace Jellyfin.Server | ||||
|         /// </summary> | ||||
|         /// <param name="app">The application builder.</param> | ||||
|         /// <param name="env">The webhost environment.</param> | ||||
|         /// <param name="serverApplicationHost">The server application host.</param> | ||||
|         public void Configure( | ||||
|             IApplicationBuilder app, | ||||
|             IWebHostEnvironment env, | ||||
|             IServerApplicationHost serverApplicationHost) | ||||
|             IWebHostEnvironment env) | ||||
|         { | ||||
|             if (env.IsDevelopment()) | ||||
|             { | ||||
| @ -120,7 +118,11 @@ namespace Jellyfin.Server | ||||
|                 app.UseHttpMetrics(); | ||||
|             } | ||||
| 
 | ||||
|             app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); | ||||
|             app.UseLanFiltering(); | ||||
|             app.UseIpBasedAccessValidation(); | ||||
|             app.UseCorsOptionsResponse(); | ||||
|             app.UseBaseUrlRedirection(); | ||||
|             app.UseWebSocketHandler(); | ||||
| 
 | ||||
|             app.UseEndpoints(endpoints => | ||||
|             { | ||||
| @ -131,6 +133,8 @@ namespace Jellyfin.Server | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             app.UseServerStartupMessage(); | ||||
| 
 | ||||
|             // Add type descriptor for legacy datetime parsing. | ||||
|             TypeDescriptor.AddAttributes(typeof(DateTime?), new TypeConverterAttribute(typeof(DateTimeTypeConverter))); | ||||
|         } | ||||
|  | ||||
| @ -8,6 +8,12 @@ namespace MediaBrowser.Controller.Extensions | ||||
|     /// </summary> | ||||
|     public static class ConfigurationExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// The key for a setting that specifies the default redirect path | ||||
|         /// to use for requests where the URL base prefix is invalid or missing.. | ||||
|         /// </summary> | ||||
|         public const string DefaultRedirectKey = "DefaultRedirectPath"; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// The key for a setting that indicates whether the application should host web client content. | ||||
|         /// </summary> | ||||
|  | ||||
| @ -117,8 +117,7 @@ namespace MediaBrowser.Controller | ||||
|         IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo(); | ||||
| 
 | ||||
|         string ExpandVirtualPath(string path); | ||||
|         string ReverseVirtualPath(string path); | ||||
| 
 | ||||
|         Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next); | ||||
|         string ReverseVirtualPath(string path); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,50 +0,0 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | ||||
| using Jellyfin.Data.Events; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| namespace MediaBrowser.Controller.Net | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Interface IHttpServer. | ||||
|     /// </summary> | ||||
|     public interface IHttpServer | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the URL prefix. | ||||
|         /// </summary> | ||||
|         /// <value>The URL prefix.</value> | ||||
|         string[] UrlPrefixes { get; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Occurs when [web socket connected]. | ||||
|         /// </summary> | ||||
|         event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Inits this instance. | ||||
|         /// </summary> | ||||
|         void Init(IEnumerable<IWebSocketListener> listener, IEnumerable<string> urlPrefixes); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// If set, all requests will respond with this message. | ||||
|         /// </summary> | ||||
|         string GlobalResponse { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// The HTTP request handler. | ||||
|         /// </summary> | ||||
|         /// <param name="context">The current HTTP context.</param> | ||||
|         /// <param name="next">The next middleware in the ASP.NET pipeline.</param> | ||||
|         /// <returns>The task.</returns> | ||||
|         Task RequestHandler(HttpContext context, Func<Task> next); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get the default CORS headers. | ||||
|         /// </summary> | ||||
|         /// <param name="httpContext">The HTTP context of the current request.</param> | ||||
|         /// <returns>The default CORS headers for the context.</returns> | ||||
|         IDictionary<string, string> GetDefaultCorsHeaders(HttpContext httpContext); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										32
									
								
								MediaBrowser.Controller/Net/IWebSocketManager.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								MediaBrowser.Controller/Net/IWebSocketManager.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | ||||
| using Jellyfin.Data.Events; | ||||
| using Microsoft.AspNetCore.Http; | ||||
| 
 | ||||
| namespace MediaBrowser.Controller.Net | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Interface IHttpServer. | ||||
|     /// </summary> | ||||
|     public interface IWebSocketManager | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Occurs when [web socket connected]. | ||||
|         /// </summary> | ||||
|         event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Inits this instance. | ||||
|         /// </summary> | ||||
|         /// <param name="listeners">The websocket listeners.</param> | ||||
|         void Init(IEnumerable<IWebSocketListener> listeners); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// The HTTP request handler. | ||||
|         /// </summary> | ||||
|         /// <param name="context">The current HTTP context.</param> | ||||
|         /// <returns>The task.</returns> | ||||
|         Task WebSocketRequestHandler(HttpContext context); | ||||
|     } | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user