From 52194f56b5f07e3ae01e2fb6d121452e37d1e93f Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Mon, 5 Dec 2022 15:01:13 +0100 Subject: Replace != null with is not null --- Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs') diff --git a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs index cdd86e28e..24807ce38 100644 --- a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs +++ b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Server.Middleware public async Task Invoke(HttpContext httpContext) { var feature = httpContext.Features.Get(); - if (feature != null) + if (feature is not null) { httpContext.Features.Set(new UrlDecodeQueryFeature(feature)); } -- cgit v1.2.3 From 74a07f6d1c52533aa648da27cb3924d6bb41bb19 Mon Sep 17 00:00:00 2001 From: Patrick Barron Date: Fri, 13 Jan 2023 21:37:37 -0500 Subject: Move Middleware to Jellyfin.Api --- .../Middleware/BaseUrlRedirectionMiddleware.cs | 81 +++++++++++ Jellyfin.Api/Middleware/ExceptionMiddleware.cs | 151 +++++++++++++++++++++ .../IpBasedAccessValidationMiddleware.cs | 50 +++++++ Jellyfin.Api/Middleware/LanFilteringMiddleware.cs | 45 ++++++ .../Middleware/LegacyEmbyRouteRewriteMiddleware.cs | 54 ++++++++ .../Middleware/QueryStringDecodingMiddleware.cs | 39 ++++++ Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs | 69 ++++++++++ .../Middleware/RobotsRedirectionMiddleware.cs | 47 +++++++ .../Middleware/ServerStartupMessageMiddleware.cs | 51 +++++++ Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs | 84 ++++++++++++ .../Middleware/WebSocketHandlerMiddleware.cs | 40 ++++++ .../Extensions/ApiApplicationBuilderExtensions.cs | 2 +- .../Middleware/BaseUrlRedirectionMiddleware.cs | 81 ----------- Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 151 --------------------- .../IpBasedAccessValidationMiddleware.cs | 50 ------- .../Middleware/LanFilteringMiddleware.cs | 45 ------ .../Middleware/LegacyEmbyRouteRewriteMiddleware.cs | 54 -------- .../Middleware/QueryStringDecodingMiddleware.cs | 39 ------ .../Middleware/ResponseTimeMiddleware.cs | 69 ---------- .../Middleware/RobotsRedirectionMiddleware.cs | 47 ------- .../Middleware/ServerStartupMessageMiddleware.cs | 51 ------- .../Middleware/UrlDecodeQueryFeature.cs | 84 ------------ .../Middleware/WebSocketHandlerMiddleware.cs | 40 ------ Jellyfin.Server/Startup.cs | 2 +- .../UrlDecodeQueryFeatureTests.cs | 2 +- 25 files changed, 714 insertions(+), 714 deletions(-) create mode 100644 Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/ExceptionMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/LanFilteringMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/LegacyEmbyRouteRewriteMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs create mode 100644 Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs create mode 100644 Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/ExceptionMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/LanFilteringMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs delete mode 100644 Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs delete mode 100644 Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs (limited to 'Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs') diff --git a/Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs new file mode 100644 index 000000000..6bd9e0b08 --- /dev/null +++ b/Jellyfin.Api/Middleware/BaseUrlRedirectionMiddleware.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading.Tasks; +using Jellyfin.Networking.Configuration; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Redirect requests without baseurl prefix to the baseurl prefixed URL. + /// + public class BaseUrlRedirectionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IConfiguration _configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + /// The logger. + /// The application configuration. + public BaseUrlRedirectionMiddleware( + RequestDelegate next, + ILogger logger, + IConfiguration configuration) + { + _next = next; + _logger = logger; + _configuration = configuration; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The server configuration manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, IServerConfigurationManager serverConfigurationManager) + { + var localPath = httpContext.Request.Path.ToString(); + var baseUrlPrefix = serverConfigurationManager.GetNetworkConfiguration().BaseUrl; + + if (string.IsNullOrEmpty(localPath) + || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix + "/web", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, baseUrlPrefix + "/web/", StringComparison.OrdinalIgnoreCase) + || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase) + ) + { + // Redirect health endpoint + if (string.Equals(localPath, "/health", StringComparison.OrdinalIgnoreCase) + || string.Equals(localPath, "/health/", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Redirecting /health check"); + httpContext.Response.Redirect(baseUrlPrefix + "/health"); + return; + } + + // Always redirect back to the default path if the base prefix is invalid or missing + _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); + + var port = httpContext.Request.Host.Port ?? -1; + var uri = new UriBuilder(httpContext.Request.Scheme, httpContext.Request.Host.Host, port, localPath).Uri; + var redirectUri = new UriBuilder(httpContext.Request.Scheme, httpContext.Request.Host.Host, port, baseUrlPrefix + "/" + _configuration[DefaultRedirectKey]).Uri; + var target = uri.MakeRelativeUri(redirectUri).ToString(); + _logger.LogDebug("Redirecting to {Target}", target); + + httpContext.Response.Redirect(target); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/ExceptionMiddleware.cs b/Jellyfin.Api/Middleware/ExceptionMiddleware.cs new file mode 100644 index 000000000..6b3aeb187 --- /dev/null +++ b/Jellyfin.Api/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,151 @@ +using System; +using System.IO; +using System.Net.Mime; +using System.Net.Sockets; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Exception Middleware. + /// + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + private readonly IServerConfigurationManager _configuration; + private readonly IWebHostEnvironment _hostEnvironment; + + /// + /// Initializes a new instance of the class. + /// + /// Next request delegate. + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public ExceptionMiddleware( + RequestDelegate next, + ILogger logger, + IServerConfigurationManager serverConfigurationManager, + IWebHostEnvironment hostEnvironment) + { + _next = next; + _logger = logger; + _configuration = serverConfigurationManager; + _hostEnvironment = hostEnvironment; + } + + /// + /// Invoke request. + /// + /// Request context. + /// Task. + public async Task Invoke(HttpContext context) + { + try + { + await _next(context).ConfigureAwait(false); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + _logger.LogWarning("The response has already started, the exception middleware will not be executed."); + throw; + } + + ex = GetActualException(ex); + + bool ignoreStackTrace = + ex is SocketException + || ex is IOException + || ex is OperationCanceledException + || ex is SecurityException + || ex is AuthenticationException + || ex is FileNotFoundException; + + if (ignoreStackTrace) + { + _logger.LogError( + "Error processing request: {ExceptionMessage}. URL {Method} {Url}.", + ex.Message.TrimEnd('.'), + context.Request.Method, + context.Request.Path); + } + else + { + _logger.LogError( + ex, + "Error processing request. URL {Method} {Url}.", + context.Request.Method, + context.Request.Path); + } + + context.Response.StatusCode = GetStatusCode(ex); + context.Response.ContentType = MediaTypeNames.Text.Plain; + + // Don't send exception unless the server is in a Development environment + var errorContent = _hostEnvironment.IsDevelopment() + ? NormalizeExceptionMessage(ex.Message) + : "Error processing request."; + await context.Response.WriteAsync(errorContent).ConfigureAwait(false); + } + } + + private static Exception GetActualException(Exception ex) + { + if (ex is AggregateException agg) + { + var inner = agg.InnerException; + if (inner is not null) + { + return GetActualException(inner); + } + + var inners = agg.InnerExceptions; + if (inners.Count > 0) + { + return GetActualException(inners[0]); + } + } + + return ex; + } + + private static int GetStatusCode(Exception ex) + { + switch (ex) + { + case ArgumentException _: return StatusCodes.Status400BadRequest; + case AuthenticationException _: return StatusCodes.Status401Unauthorized; + case SecurityException _: return StatusCodes.Status403Forbidden; + case DirectoryNotFoundException _: + case FileNotFoundException _: + case ResourceNotFoundException _: return StatusCodes.Status404NotFound; + case MethodNotAllowedException _: return StatusCodes.Status405MethodNotAllowed; + default: return StatusCodes.Status500InternalServerError; + } + } + + private string NormalizeExceptionMessage(string msg) + { + // Strip any information we don't want to reveal + return msg.Replace( + _configuration.ApplicationPaths.ProgramSystemPath, + string.Empty, + StringComparison.OrdinalIgnoreCase) + .Replace( + _configuration.ApplicationPaths.ProgramDataPath, + string.Empty, + StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs new file mode 100644 index 000000000..f7af91e48 --- /dev/null +++ b/Jellyfin.Api/Middleware/IpBasedAccessValidationMiddleware.cs @@ -0,0 +1,50 @@ +using System.Net; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Validates the IP of requests coming from local networks wrt. remote access. + /// + public class IpBasedAccessValidationMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public IpBasedAccessValidationMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The network manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager) + { + if (httpContext.IsLocal()) + { + // Running locally. + await _next(httpContext).ConfigureAwait(false); + return; + } + + var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback; + + if (!networkManager.HasRemoteAccess(remoteIp)) + { + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs new file mode 100644 index 000000000..18f13bbce --- /dev/null +++ b/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs @@ -0,0 +1,45 @@ +using System.Net; +using System.Threading.Tasks; +using Jellyfin.Networking.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Validates the LAN host IP based on application configuration. + /// + public class LanFilteringMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public LanFilteringMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The network manager. + /// The server configuration manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) + { + var host = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback; + + if (!networkManager.IsInLocalNetwork(host) && !serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess) + { + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/LegacyEmbyRouteRewriteMiddleware.cs b/Jellyfin.Api/Middleware/LegacyEmbyRouteRewriteMiddleware.cs new file mode 100644 index 000000000..b73923c1e --- /dev/null +++ b/Jellyfin.Api/Middleware/LegacyEmbyRouteRewriteMiddleware.cs @@ -0,0 +1,54 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Removes /emby and /mediabrowser from requested route. + /// + public class LegacyEmbyRouteRewriteMiddleware + { + private const string EmbyPath = "/emby"; + private const string MediabrowserPath = "/mediabrowser"; + + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + /// The logger. + public LegacyEmbyRouteRewriteMiddleware( + RequestDelegate next, + ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The async task. + public async Task Invoke(HttpContext httpContext) + { + var localPath = httpContext.Request.Path.ToString(); + if (localPath.StartsWith(EmbyPath, StringComparison.OrdinalIgnoreCase)) + { + httpContext.Request.Path = localPath[EmbyPath.Length..]; + _logger.LogDebug("Removing {EmbyPath} from route.", EmbyPath); + } + else if (localPath.StartsWith(MediabrowserPath, StringComparison.OrdinalIgnoreCase)) + { + httpContext.Request.Path = localPath[MediabrowserPath.Length..]; + _logger.LogDebug("Removing {MediabrowserPath} from route.", MediabrowserPath); + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs new file mode 100644 index 000000000..4b6304e0e --- /dev/null +++ b/Jellyfin.Api/Middleware/QueryStringDecodingMiddleware.cs @@ -0,0 +1,39 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; + +namespace Jellyfin.Api.Middleware +{ + /// + /// URL decodes the querystring before binding. + /// + public class QueryStringDecodingMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public QueryStringDecodingMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The async task. + public async Task Invoke(HttpContext httpContext) + { + var feature = httpContext.Features.Get(); + if (feature is not null) + { + httpContext.Features.Set(new UrlDecodeQueryFeature(feature)); + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs new file mode 100644 index 000000000..3701d0f45 --- /dev/null +++ b/Jellyfin.Api/Middleware/ResponseTimeMiddleware.cs @@ -0,0 +1,69 @@ +using System.Diagnostics; +using System.Globalization; +using System.Threading.Tasks; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Response time middleware. + /// + public class ResponseTimeMiddleware + { + private const string ResponseHeaderResponseTime = "X-Response-Time-ms"; + + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Next request delegate. + /// Instance of the interface. + public ResponseTimeMiddleware( + RequestDelegate next, + ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Invoke request. + /// + /// Request context. + /// Instance of the interface. + /// Task. + public async Task Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager) + { + var startTimestamp = Stopwatch.GetTimestamp(); + + var enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning; + var warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs; + context.Response.OnStarting(() => + { + var responseTime = Stopwatch.GetElapsedTime(startTimestamp); + var responseTimeMs = responseTime.TotalMilliseconds; + if (enableWarning && responseTimeMs > warningThreshold && _logger.IsEnabled(LogLevel.Debug)) + { + _logger.LogDebug( + "Slow HTTP Response from {Url} to {RemoteIp} in {Elapsed:g} with Status Code {StatusCode}", + context.Request.GetDisplayUrl(), + context.GetNormalizedRemoteIp(), + responseTime, + context.Response.StatusCode); + } + + context.Response.Headers[ResponseHeaderResponseTime] = responseTimeMs.ToString(CultureInfo.InvariantCulture); + return Task.CompletedTask; + }); + + // Call the next delegate/middleware in the pipeline + await this._next(context).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs b/Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs new file mode 100644 index 000000000..2e69580be --- /dev/null +++ b/Jellyfin.Api/Middleware/RobotsRedirectionMiddleware.cs @@ -0,0 +1,47 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Redirect requests to robots.txt to web/robots.txt. + /// + public class RobotsRedirectionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + /// The logger. + public RobotsRedirectionMiddleware( + RequestDelegate next, + ILogger logger) + { + _next = next; + _logger = logger; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The async task. + public async Task Invoke(HttpContext httpContext) + { + var localPath = httpContext.Request.Path.ToString(); + if (string.Equals(localPath, "/robots.txt", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Redirecting robots.txt request to web/robots.txt"); + httpContext.Response.Redirect("web/robots.txt"); + return; + } + + await _next(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs b/Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs new file mode 100644 index 000000000..dcd64401a --- /dev/null +++ b/Jellyfin.Api/Middleware/ServerStartupMessageMiddleware.cs @@ -0,0 +1,51 @@ +using System; +using System.Net.Mime; +using System.Threading.Tasks; +using MediaBrowser.Controller; +using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Shows a custom message during server startup. + /// + public class ServerStartupMessageMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public ServerStartupMessageMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The server application host. + /// The localization manager. + /// The async task. + public async Task Invoke( + HttpContext httpContext, + IServerApplicationHost serverApplicationHost, + ILocalizationManager localizationManager) + { + if (serverApplicationHost.CoreStartupHasCompleted + || httpContext.Request.Path.Equals("/system/ping", StringComparison.OrdinalIgnoreCase)) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + 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); + } + } +} diff --git a/Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs new file mode 100644 index 000000000..d35e0fcfd --- /dev/null +++ b/Jellyfin.Api/Middleware/UrlDecodeQueryFeature.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Jellyfin.Extensions; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.Primitives; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Defines the . + /// + public class UrlDecodeQueryFeature : IQueryFeature + { + private IQueryCollection? _store; + + /// + /// Initializes a new instance of the class. + /// + /// The instance. + public UrlDecodeQueryFeature(IQueryFeature feature) + { + Query = feature.Query; + } + + /// + /// Gets or sets a value indicating the url decoded . + /// + public IQueryCollection Query + { + get + { + return _store ?? QueryCollection.Empty; + } + + set + { + // Only interested in where the querystring is encoded which shows up as one key with nothing in the value. + if (value.Count != 1) + { + _store = value; + return; + } + + // Encoded querystrings have no value, so don't process anything if a value is present. + var (key, stringValues) = value.First(); + if (!string.IsNullOrEmpty(stringValues)) + { + _store = value; + return; + } + + if (!key.Contains('=', StringComparison.Ordinal)) + { + _store = value; + return; + } + + var pairs = new Dictionary(); + foreach (var pair in key.SpanSplit('&')) + { + var i = pair.IndexOf('='); + if (i == -1) + { + // encoded is an equals. + // We use TryAdd so duplicate keys get ignored + pairs.TryAdd(pair.ToString(), StringValues.Empty); + continue; + } + + var k = pair[..i].ToString(); + var v = pair[(i + 1)..].ToString(); + if (!pairs.TryAdd(k, new StringValues(v))) + { + pairs[k] = StringValues.Concat(pairs[k], v); + } + } + + _store = new QueryCollection(pairs); + } + } + } +} diff --git a/Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs new file mode 100644 index 000000000..2cf1e5e4a --- /dev/null +++ b/Jellyfin.Api/Middleware/WebSocketHandlerMiddleware.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Middleware +{ + /// + /// Handles WebSocket requests. + /// + public class WebSocketHandlerMiddleware + { + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + /// The next delegate in the pipeline. + public WebSocketHandlerMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Executes the middleware action. + /// + /// The current HTTP context. + /// The WebSocket connection manager. + /// The async task. + public async Task Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) + { + if (!httpContext.WebSockets.IsWebSocketRequest) + { + await _next(httpContext).ConfigureAwait(false); + return; + } + + await webSocketManager.WebSocketRequestHandler(httpContext).ConfigureAwait(false); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs index e29167747..463ca7321 100644 --- a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; +using Jellyfin.Api.Middleware; using Jellyfin.Networking.Configuration; -using Jellyfin.Server.Middleware; using MediaBrowser.Controller.Configuration; using Microsoft.AspNetCore.Builder; using Microsoft.OpenApi.Models; diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs deleted file mode 100644 index 6ee5bf38a..000000000 --- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading.Tasks; -using Jellyfin.Networking.Configuration; -using MediaBrowser.Controller.Configuration; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using static MediaBrowser.Controller.Extensions.ConfigurationExtensions; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Redirect requests without baseurl prefix to the baseurl prefixed URL. - /// - public class BaseUrlRedirectionMiddleware - { - private readonly RequestDelegate _next; - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - /// The logger. - /// The application configuration. - public BaseUrlRedirectionMiddleware( - RequestDelegate next, - ILogger logger, - IConfiguration configuration) - { - _next = next; - _logger = logger; - _configuration = configuration; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The server configuration manager. - /// The async task. - public async Task Invoke(HttpContext httpContext, IServerConfigurationManager serverConfigurationManager) - { - var localPath = httpContext.Request.Path.ToString(); - var baseUrlPrefix = serverConfigurationManager.GetNetworkConfiguration().BaseUrl; - - if (string.IsNullOrEmpty(localPath) - || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, baseUrlPrefix + "/web", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, baseUrlPrefix + "/web/", StringComparison.OrdinalIgnoreCase) - || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase) - ) - { - // Redirect health endpoint - if (string.Equals(localPath, "/health", StringComparison.OrdinalIgnoreCase) - || string.Equals(localPath, "/health/", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogDebug("Redirecting /health check"); - httpContext.Response.Redirect(baseUrlPrefix + "/health"); - return; - } - - // Always redirect back to the default path if the base prefix is invalid or missing - _logger.LogDebug("Normalizing an URL at {LocalPath}", localPath); - - var port = httpContext.Request.Host.Port ?? -1; - var uri = new UriBuilder(httpContext.Request.Scheme, httpContext.Request.Host.Host, port, localPath).Uri; - var redirectUri = new UriBuilder(httpContext.Request.Scheme, httpContext.Request.Host.Host, port, baseUrlPrefix + "/" + _configuration[DefaultRedirectKey]).Uri; - var target = uri.MakeRelativeUri(redirectUri).ToString(); - _logger.LogDebug("Redirecting to {Target}", target); - - httpContext.Response.Redirect(target); - return; - } - - await _next(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs deleted file mode 100644 index 91dbce19a..000000000 --- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.IO; -using System.Net.Mime; -using System.Net.Sockets; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Net; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Exception Middleware. - /// - public class ExceptionMiddleware - { - private readonly RequestDelegate _next; - private readonly ILogger _logger; - private readonly IServerConfigurationManager _configuration; - private readonly IWebHostEnvironment _hostEnvironment; - - /// - /// Initializes a new instance of the class. - /// - /// Next request delegate. - /// Instance of the interface. - /// Instance of the interface. - /// Instance of the interface. - public ExceptionMiddleware( - RequestDelegate next, - ILogger logger, - IServerConfigurationManager serverConfigurationManager, - IWebHostEnvironment hostEnvironment) - { - _next = next; - _logger = logger; - _configuration = serverConfigurationManager; - _hostEnvironment = hostEnvironment; - } - - /// - /// Invoke request. - /// - /// Request context. - /// Task. - public async Task Invoke(HttpContext context) - { - try - { - await _next(context).ConfigureAwait(false); - } - catch (Exception ex) - { - if (context.Response.HasStarted) - { - _logger.LogWarning("The response has already started, the exception middleware will not be executed."); - throw; - } - - ex = GetActualException(ex); - - bool ignoreStackTrace = - ex is SocketException - || ex is IOException - || ex is OperationCanceledException - || ex is SecurityException - || ex is AuthenticationException - || ex is FileNotFoundException; - - if (ignoreStackTrace) - { - _logger.LogError( - "Error processing request: {ExceptionMessage}. URL {Method} {Url}.", - ex.Message.TrimEnd('.'), - context.Request.Method, - context.Request.Path); - } - else - { - _logger.LogError( - ex, - "Error processing request. URL {Method} {Url}.", - context.Request.Method, - context.Request.Path); - } - - context.Response.StatusCode = GetStatusCode(ex); - context.Response.ContentType = MediaTypeNames.Text.Plain; - - // Don't send exception unless the server is in a Development environment - var errorContent = _hostEnvironment.IsDevelopment() - ? NormalizeExceptionMessage(ex.Message) - : "Error processing request."; - await context.Response.WriteAsync(errorContent).ConfigureAwait(false); - } - } - - private static Exception GetActualException(Exception ex) - { - if (ex is AggregateException agg) - { - var inner = agg.InnerException; - if (inner is not null) - { - return GetActualException(inner); - } - - var inners = agg.InnerExceptions; - if (inners.Count > 0) - { - return GetActualException(inners[0]); - } - } - - return ex; - } - - private static int GetStatusCode(Exception ex) - { - switch (ex) - { - case ArgumentException _: return StatusCodes.Status400BadRequest; - case AuthenticationException _: return StatusCodes.Status401Unauthorized; - case SecurityException _: return StatusCodes.Status403Forbidden; - case DirectoryNotFoundException _: - case FileNotFoundException _: - case ResourceNotFoundException _: return StatusCodes.Status404NotFound; - case MethodNotAllowedException _: return StatusCodes.Status405MethodNotAllowed; - default: return StatusCodes.Status500InternalServerError; - } - } - - private string NormalizeExceptionMessage(string msg) - { - // Strip any information we don't want to reveal - return msg.Replace( - _configuration.ApplicationPaths.ProgramSystemPath, - string.Empty, - StringComparison.OrdinalIgnoreCase) - .Replace( - _configuration.ApplicationPaths.ProgramDataPath, - string.Empty, - StringComparison.OrdinalIgnoreCase); - } - } -} diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs deleted file mode 100644 index 0afcd61a0..000000000 --- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Net; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using Microsoft.AspNetCore.Http; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Validates the IP of requests coming from local networks wrt. remote access. - /// - public class IpBasedAccessValidationMiddleware - { - private readonly RequestDelegate _next; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - public IpBasedAccessValidationMiddleware(RequestDelegate next) - { - _next = next; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The network manager. - /// The async task. - public async Task Invoke(HttpContext httpContext, INetworkManager networkManager) - { - if (httpContext.IsLocal()) - { - // Running locally. - await _next(httpContext).ConfigureAwait(false); - return; - } - - var remoteIp = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback; - - if (!networkManager.HasRemoteAccess(remoteIp)) - { - return; - } - - await _next(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs deleted file mode 100644 index 67bf24d2a..000000000 --- a/Jellyfin.Server/Middleware/LanFilteringMiddleware.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System.Net; -using System.Threading.Tasks; -using Jellyfin.Networking.Configuration; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using Microsoft.AspNetCore.Http; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Validates the LAN host IP based on application configuration. - /// - public class LanFilteringMiddleware - { - private readonly RequestDelegate _next; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - public LanFilteringMiddleware(RequestDelegate next) - { - _next = next; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The network manager. - /// The server configuration manager. - /// The async task. - public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager) - { - var host = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback; - - if (!networkManager.IsInLocalNetwork(host) && !serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess) - { - return; - } - - await _next(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs b/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs deleted file mode 100644 index b214299df..000000000 --- a/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Removes /emby and /mediabrowser from requested route. - /// - public class LegacyEmbyRouteRewriteMiddleware - { - private const string EmbyPath = "/emby"; - private const string MediabrowserPath = "/mediabrowser"; - - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - /// The logger. - public LegacyEmbyRouteRewriteMiddleware( - RequestDelegate next, - ILogger logger) - { - _next = next; - _logger = logger; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The async task. - public async Task Invoke(HttpContext httpContext) - { - var localPath = httpContext.Request.Path.ToString(); - if (localPath.StartsWith(EmbyPath, StringComparison.OrdinalIgnoreCase)) - { - httpContext.Request.Path = localPath[EmbyPath.Length..]; - _logger.LogDebug("Removing {EmbyPath} from route.", EmbyPath); - } - else if (localPath.StartsWith(MediabrowserPath, StringComparison.OrdinalIgnoreCase)) - { - httpContext.Request.Path = localPath[MediabrowserPath.Length..]; - _logger.LogDebug("Removing {MediabrowserPath} from route.", MediabrowserPath); - } - - await _next(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs b/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs deleted file mode 100644 index 24807ce38..000000000 --- a/Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; - -namespace Jellyfin.Server.Middleware -{ - /// - /// URL decodes the querystring before binding. - /// - public class QueryStringDecodingMiddleware - { - private readonly RequestDelegate _next; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - public QueryStringDecodingMiddleware(RequestDelegate next) - { - _next = next; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The async task. - public async Task Invoke(HttpContext httpContext) - { - var feature = httpContext.Features.Get(); - if (feature is not null) - { - httpContext.Features.Set(new UrlDecodeQueryFeature(feature)); - } - - await _next(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs deleted file mode 100644 index 531897cd4..000000000 --- a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Diagnostics; -using System.Globalization; -using System.Threading.Tasks; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Configuration; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Response time middleware. - /// - public class ResponseTimeMiddleware - { - private const string ResponseHeaderResponseTime = "X-Response-Time-ms"; - - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// Next request delegate. - /// Instance of the interface. - public ResponseTimeMiddleware( - RequestDelegate next, - ILogger logger) - { - _next = next; - _logger = logger; - } - - /// - /// Invoke request. - /// - /// Request context. - /// Instance of the interface. - /// Task. - public async Task Invoke(HttpContext context, IServerConfigurationManager serverConfigurationManager) - { - var startTimestamp = Stopwatch.GetTimestamp(); - - var enableWarning = serverConfigurationManager.Configuration.EnableSlowResponseWarning; - var warningThreshold = serverConfigurationManager.Configuration.SlowResponseThresholdMs; - context.Response.OnStarting(() => - { - var responseTime = Stopwatch.GetElapsedTime(startTimestamp); - var responseTimeMs = responseTime.TotalMilliseconds; - if (enableWarning && responseTimeMs > warningThreshold && _logger.IsEnabled(LogLevel.Debug)) - { - _logger.LogDebug( - "Slow HTTP Response from {Url} to {RemoteIp} in {Elapsed:g} with Status Code {StatusCode}", - context.Request.GetDisplayUrl(), - context.GetNormalizedRemoteIp(), - responseTime, - context.Response.StatusCode); - } - - context.Response.Headers[ResponseHeaderResponseTime] = responseTimeMs.ToString(CultureInfo.InvariantCulture); - return Task.CompletedTask; - }); - - // Call the next delegate/middleware in the pipeline - await this._next(context).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs deleted file mode 100644 index fabcd2da7..000000000 --- a/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Redirect requests to robots.txt to web/robots.txt. - /// - public class RobotsRedirectionMiddleware - { - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - /// The logger. - public RobotsRedirectionMiddleware( - RequestDelegate next, - ILogger logger) - { - _next = next; - _logger = logger; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The async task. - public async Task Invoke(HttpContext httpContext) - { - var localPath = httpContext.Request.Path.ToString(); - if (string.Equals(localPath, "/robots.txt", StringComparison.OrdinalIgnoreCase)) - { - _logger.LogDebug("Redirecting robots.txt request to web/robots.txt"); - httpContext.Response.Redirect("web/robots.txt"); - return; - } - - await _next(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs deleted file mode 100644 index 2ec063392..000000000 --- a/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Net.Mime; -using System.Threading.Tasks; -using MediaBrowser.Controller; -using MediaBrowser.Model.Globalization; -using Microsoft.AspNetCore.Http; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Shows a custom message during server startup. - /// - public class ServerStartupMessageMiddleware - { - private readonly RequestDelegate _next; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - public ServerStartupMessageMiddleware(RequestDelegate next) - { - _next = next; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The server application host. - /// The localization manager. - /// The async task. - public async Task Invoke( - HttpContext httpContext, - IServerApplicationHost serverApplicationHost, - ILocalizationManager localizationManager) - { - if (serverApplicationHost.CoreStartupHasCompleted - || httpContext.Request.Path.Equals("/system/ping", StringComparison.OrdinalIgnoreCase)) - { - await _next(httpContext).ConfigureAwait(false); - return; - } - - 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); - } - } -} diff --git a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs b/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs deleted file mode 100644 index 2f1d79157..000000000 --- a/Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Jellyfin.Extensions; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.Extensions.Primitives; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Defines the . - /// - public class UrlDecodeQueryFeature : IQueryFeature - { - private IQueryCollection? _store; - - /// - /// Initializes a new instance of the class. - /// - /// The instance. - public UrlDecodeQueryFeature(IQueryFeature feature) - { - Query = feature.Query; - } - - /// - /// Gets or sets a value indicating the url decoded . - /// - public IQueryCollection Query - { - get - { - return _store ?? QueryCollection.Empty; - } - - set - { - // Only interested in where the querystring is encoded which shows up as one key with nothing in the value. - if (value.Count != 1) - { - _store = value; - return; - } - - // Encoded querystrings have no value, so don't process anything if a value is present. - var (key, stringValues) = value.First(); - if (!string.IsNullOrEmpty(stringValues)) - { - _store = value; - return; - } - - if (!key.Contains('=', StringComparison.Ordinal)) - { - _store = value; - return; - } - - var pairs = new Dictionary(); - foreach (var pair in key.SpanSplit('&')) - { - var i = pair.IndexOf('='); - if (i == -1) - { - // encoded is an equals. - // We use TryAdd so duplicate keys get ignored - pairs.TryAdd(pair.ToString(), StringValues.Empty); - continue; - } - - var k = pair[..i].ToString(); - var v = pair[(i + 1)..].ToString(); - if (!pairs.TryAdd(k, new StringValues(v))) - { - pairs[k] = StringValues.Concat(pairs[k], v); - } - } - - _store = new QueryCollection(pairs); - } - } - } -} diff --git a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs b/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs deleted file mode 100644 index b7a5d2b34..000000000 --- a/Jellyfin.Server/Middleware/WebSocketHandlerMiddleware.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Threading.Tasks; -using MediaBrowser.Controller.Net; -using Microsoft.AspNetCore.Http; - -namespace Jellyfin.Server.Middleware -{ - /// - /// Handles WebSocket requests. - /// - public class WebSocketHandlerMiddleware - { - private readonly RequestDelegate _next; - - /// - /// Initializes a new instance of the class. - /// - /// The next delegate in the pipeline. - public WebSocketHandlerMiddleware(RequestDelegate next) - { - _next = next; - } - - /// - /// Executes the middleware action. - /// - /// The current HTTP context. - /// The WebSocket connection manager. - /// The async task. - public async Task Invoke(HttpContext httpContext, IWebSocketManager webSocketManager) - { - if (!httpContext.WebSockets.IsWebSocketRequest) - { - await _next(httpContext).ConfigureAwait(false); - return; - } - - await webSocketManager.WebSocketRequestHandler(httpContext).ConfigureAwait(false); - } - } -} diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs index f89f81c76..0062b8c05 100644 --- a/Jellyfin.Server/Startup.cs +++ b/Jellyfin.Server/Startup.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.Text; +using Jellyfin.Api.Middleware; using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.Networking.Configuration; using Jellyfin.Server.Extensions; @@ -12,7 +13,6 @@ using Jellyfin.Server.HealthChecks; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Extensions; using Jellyfin.Server.Infrastructure; -using Jellyfin.Server.Middleware; using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; diff --git a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs index d15c9d6f5..797fc8f64 100644 --- a/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs +++ b/tests/Jellyfin.Server.Tests/UrlDecodeQueryFeatureTests.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Jellyfin.Server.Middleware; +using Jellyfin.Api.Middleware; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Primitives; -- cgit v1.2.3