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 --- Jellyfin.Api/Middleware/ExceptionMiddleware.cs | 151 +++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 Jellyfin.Api/Middleware/ExceptionMiddleware.cs (limited to 'Jellyfin.Api/Middleware/ExceptionMiddleware.cs') 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); + } + } +} -- cgit v1.2.3