mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-10 01:38:49 +01:00
Merge remote-tracking branch 'upstream/master' into client-logger
This commit is contained in:
@@ -23,7 +23,7 @@ namespace Jellyfin.Server.Configuration
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<CorsPolicy> GetPolicyAsync(HttpContext context, string policyName)
|
||||
public Task<CorsPolicy?> GetPolicyAsync(HttpContext context, string? policyName)
|
||||
{
|
||||
var corsHosts = _serverConfigurationManager.Configuration.CorsHosts;
|
||||
var builder = new CorsPolicyBuilder()
|
||||
@@ -43,7 +43,7 @@ namespace Jellyfin.Server.Configuration
|
||||
.AllowCredentials();
|
||||
}
|
||||
|
||||
return Task.FromResult(builder.Build());
|
||||
return Task.FromResult<CorsPolicy?>(builder.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,18 @@ using Jellyfin.Api.WebSocketListeners;
|
||||
using Jellyfin.Drawing.Skia;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.Devices;
|
||||
using Jellyfin.Server.Implementations.Events;
|
||||
using Jellyfin.Server.Implementations.Security;
|
||||
using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.BaseItemManager;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -74,7 +78,9 @@ namespace Jellyfin.Server
|
||||
}
|
||||
|
||||
ServiceCollection.AddDbContextPool<JellyfinDb>(
|
||||
options => options.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
||||
options => options
|
||||
.UseLoggerFactory(LoggerFactory)
|
||||
.UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"));
|
||||
|
||||
ServiceCollection.AddEventServices();
|
||||
ServiceCollection.AddSingleton<IBaseItemManager, BaseItemManager>();
|
||||
@@ -84,6 +90,7 @@ namespace Jellyfin.Server
|
||||
ServiceCollection.AddSingleton<IActivityManager, ActivityManager>();
|
||||
ServiceCollection.AddSingleton<IUserManager, UserManager>();
|
||||
ServiceCollection.AddSingleton<IDisplayPreferencesManager, DisplayPreferencesManager>();
|
||||
ServiceCollection.AddSingleton<IDeviceManager, DeviceManager>();
|
||||
|
||||
// TODO search the assemblies instead of adding them manually?
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionWebSocketListener>();
|
||||
@@ -91,6 +98,10 @@ namespace Jellyfin.Server
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, ScheduledTasksWebSocketListener>();
|
||||
ServiceCollection.AddSingleton<IWebSocketListener, SessionInfoWebSocketListener>();
|
||||
|
||||
ServiceCollection.AddSingleton<IAuthorizationContext, AuthorizationContext>();
|
||||
|
||||
ServiceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
||||
|
||||
base.RegisterServices();
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,16 @@ namespace Jellyfin.Server.Extensions
|
||||
return appBuilder.UseMiddleware<LanFilteringMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables url decoding before binding to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseQueryStringDecoding(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<QueryStringDecodingMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds base url redirection to the application pipeline.
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using Emby.Server.Implementations;
|
||||
using Jellyfin.Api.Auth;
|
||||
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
|
||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Api.Auth.DownloadPolicy;
|
||||
using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy;
|
||||
@@ -21,11 +22,11 @@ using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Controllers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using Jellyfin.Server.Formatters;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
@@ -61,6 +62,7 @@ namespace Jellyfin.Server.Extensions
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreParentalControlHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeOrIgnoreParentalControlSetupHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>();
|
||||
@@ -157,6 +159,13 @@ namespace Jellyfin.Server.Extensions
|
||||
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
|
||||
policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup));
|
||||
});
|
||||
options.AddPolicy(
|
||||
Policies.AnonymousLanAccessPolicy,
|
||||
policy =>
|
||||
{
|
||||
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
|
||||
policy.AddRequirements(new AnonymousLanAccessRequirement());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,7 +197,8 @@ namespace Jellyfin.Server.Extensions
|
||||
// https://github.com/dotnet/aspnetcore/blob/master/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
|
||||
// Enable debug logging on Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware to help investigate issues.
|
||||
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
|
||||
if (config.KnownProxies.Length == 0)
|
||||
{
|
||||
options.KnownNetworks.Clear();
|
||||
@@ -278,7 +288,7 @@ namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
In = ParameterLocation.Header,
|
||||
Name = "X-Emby-Authorization",
|
||||
Name = "Authorization",
|
||||
Description = "API key header parameter"
|
||||
});
|
||||
|
||||
@@ -303,7 +313,7 @@ namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
description.TryGetMethodInfo(out MethodInfo methodInfo);
|
||||
// Attribute name, method name, none.
|
||||
return description?.ActionDescriptor?.AttributeRouteInfo?.Name
|
||||
return description?.ActionDescriptor.AttributeRouteInfo?.Name
|
||||
?? methodInfo?.Name
|
||||
?? null;
|
||||
});
|
||||
@@ -341,7 +351,7 @@ namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
foreach (var address in host.GetAddresses())
|
||||
{
|
||||
AddIpAddress(config, options, addr.Address, addr.PrefixLength);
|
||||
AddIpAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? 32 : 128);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,7 +407,7 @@ namespace Jellyfin.Server.Extensions
|
||||
Type = "object",
|
||||
Properties = typeof(ImageType).GetEnumNames().ToDictionary(
|
||||
name => name,
|
||||
name => new OpenApiSchema
|
||||
_ => new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using MediaBrowser.Common.Json;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Net.Mime;
|
||||
using MediaBrowser.Common.Json;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) .NET Foundation and Contributors
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace Jellyfin.Server.Infrastructure
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class SymlinkFollowingPhysicalFileResultExecutor : PhysicalFileResultExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SymlinkFollowingPhysicalFileResultExecutor"/> class.
|
||||
/// </summary>
|
||||
/// <param name="loggerFactory">An instance of the <see cref="ILoggerFactory"/> interface.</param>
|
||||
public SymlinkFollowingPhysicalFileResultExecutor(ILoggerFactory loggerFactory) : base(loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override FileMetadata GetFileInfo(string path)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var length = fileInfo.Length;
|
||||
// This may or may not be fixed in .NET 6, but looks like it will not https://github.com/dotnet/aspnetcore/issues/34371
|
||||
if ((fileInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
|
||||
{
|
||||
using var fileHandle = File.OpenHandle(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
length = RandomAccess.GetLength(fileHandle);
|
||||
}
|
||||
|
||||
return new FileMetadata
|
||||
{
|
||||
Exists = fileInfo.Exists,
|
||||
Length = length,
|
||||
LastModified = fileInfo.LastWriteTimeUtc
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task WriteFileAsync(ActionContext context, PhysicalFileResult result, RangeItemHeaderValue? range, long rangeLength)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(result));
|
||||
}
|
||||
|
||||
if (range != null && rangeLength == 0)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// It's a bit of wasted IO to perform this check again, but non-symlinks shouldn't use this code
|
||||
if (!IsSymLink(result.FileName))
|
||||
{
|
||||
return base.WriteFileAsync(context, result, range, rangeLength);
|
||||
}
|
||||
|
||||
var response = context.HttpContext.Response;
|
||||
|
||||
if (range != null)
|
||||
{
|
||||
return SendFileAsync(
|
||||
result.FileName,
|
||||
response,
|
||||
offset: range.From ?? 0L,
|
||||
count: rangeLength);
|
||||
}
|
||||
|
||||
return SendFileAsync(
|
||||
result.FileName,
|
||||
response,
|
||||
offset: 0,
|
||||
count: null);
|
||||
}
|
||||
|
||||
private async Task SendFileAsync(string filePath, HttpResponse response, long offset, long? count)
|
||||
{
|
||||
var fileInfo = GetFileInfo(filePath);
|
||||
if (offset < 0 || offset > fileInfo.Length)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
|
||||
}
|
||||
|
||||
if (count.HasValue
|
||||
&& (count.Value < 0 || count.Value > fileInfo.Length - offset))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
|
||||
}
|
||||
|
||||
// Copied from SendFileFallback.SendFileAsync
|
||||
const int BufferSize = 1024 * 16;
|
||||
|
||||
await using var fileStream = new FileStream(
|
||||
filePath,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite,
|
||||
bufferSize: BufferSize,
|
||||
options: FileOptions.Asynchronous | FileOptions.SequentialScan);
|
||||
|
||||
fileStream.Seek(offset, SeekOrigin.Begin);
|
||||
await StreamCopyOperation
|
||||
.CopyToAsync(fileStream, response.Body, count, BufferSize, CancellationToken.None)
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private static bool IsSymLink(string path) => (File.GetAttributes(path) & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,10 @@
|
||||
<PropertyGroup>
|
||||
<AssemblyName>jellyfin</AssemblyName>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ServerGarbageCollection>false</ServerGarbageCollection>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
<!-- <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers> -->
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -36,21 +31,21 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.5" />
|
||||
<PackageReference Include="prometheus-net" Version="4.1.1" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0-rc.2*" />
|
||||
<PackageReference Include="prometheus-net" Version="5.0.1" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
|
||||
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
|
||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -45,15 +45,36 @@ namespace Jellyfin.Server.Middleware
|
||||
var localPath = httpContext.Request.Path.ToString();
|
||||
var baseUrlPrefix = serverConfigurationManager.GetNetworkConfiguration().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))
|
||||
if (!string.IsNullOrEmpty(baseUrlPrefix))
|
||||
{
|
||||
// Always redirect back to the default path if the base prefix is invalid or missing
|
||||
var startsWithBaseUrl = localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!startsWithBaseUrl
|
||||
&& (localPath.Equals("/health", StringComparison.OrdinalIgnoreCase)
|
||||
|| localPath.Equals("/health/", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
_logger.LogDebug("Redirecting /health check");
|
||||
httpContext.Response.Redirect(baseUrlPrefix + "/health");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!startsWithBaseUrl
|
||||
|| localPath.Length == baseUrlPrefix.Length
|
||||
// Local path is /baseUrl/
|
||||
|| (localPath.Length == baseUrlPrefix.Length + 1 && localPath[^1] == '/'))
|
||||
{
|
||||
// Always redirect back to the default path if the base prefix is invalid, missing, or is the full path.
|
||||
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
|
||||
httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (string.IsNullOrEmpty(localPath)
|
||||
|| localPath.Equals("/", StringComparison.Ordinal))
|
||||
{
|
||||
// Always redirect back to the default path if root is requested.
|
||||
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
|
||||
httpContext.Response.Redirect(baseUrlPrefix + "/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
|
||||
httpContext.Response.Redirect("/" + _configuration[ConfigurationExtensions.DefaultRedirectKey]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,11 +137,6 @@ namespace Jellyfin.Server.Middleware
|
||||
|
||||
private string NormalizeExceptionMessage(string msg)
|
||||
{
|
||||
if (msg == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// Strip any information we don't want to reveal
|
||||
return msg.Replace(
|
||||
_configuration.ApplicationPaths.ProgramSystemPath,
|
||||
|
||||
39
Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs
Normal file
39
Jellyfin.Server/Middleware/QueryStringDecodingMiddleware.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Jellyfin.Server.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// URL decodes the querystring before binding.
|
||||
/// </summary>
|
||||
public class QueryStringDecodingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QueryStringDecodingMiddleware"/> class.
|
||||
/// </summary>
|
||||
/// <param name="next">The next delegate in the pipeline.</param>
|
||||
public QueryStringDecodingMiddleware(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)
|
||||
{
|
||||
var feature = httpContext.Features.Get<IQueryFeature>();
|
||||
if (feature != null)
|
||||
{
|
||||
httpContext.Features.Set<IQueryFeature>(new UrlDecodeQueryFeature(feature));
|
||||
}
|
||||
|
||||
await _next(httpContext).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
84
Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs
Normal file
84
Jellyfin.Server/Middleware/UrlDecodeQueryFeature.cs
Normal file
@@ -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.Server.Middleware
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="UrlDecodeQueryFeature"/>.
|
||||
/// </summary>
|
||||
public class UrlDecodeQueryFeature : IQueryFeature
|
||||
{
|
||||
private IQueryCollection? _store;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UrlDecodeQueryFeature"/> class.
|
||||
/// </summary>
|
||||
/// <param name="feature">The <see cref="IQueryFeature"/> instance.</param>
|
||||
public UrlDecodeQueryFeature(IQueryFeature feature)
|
||||
{
|
||||
Query = feature.Query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating the url decoded <see cref="IQueryCollection"/>.
|
||||
/// </summary>
|
||||
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('='))
|
||||
{
|
||||
_store = value;
|
||||
return;
|
||||
}
|
||||
|
||||
var pairs = new Dictionary<string, StringValues>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,8 @@ namespace Jellyfin.Server.Migrations
|
||||
typeof(Routines.ReaddDefaultPluginRepository),
|
||||
typeof(Routines.MigrateDisplayPreferencesDb),
|
||||
typeof(Routines.RemoveDownloadImagesInAdvance),
|
||||
typeof(Routines.AddPeopleQueryIndex)
|
||||
typeof(Routines.AddPeopleQueryIndex),
|
||||
typeof(Routines.MigrateAuthenticationDb)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -40,7 +41,7 @@ namespace Jellyfin.Server.Migrations
|
||||
.Select(m => ActivatorUtilities.CreateInstance(host.ServiceProvider, m))
|
||||
.OfType<IMigrationRoutine>()
|
||||
.ToArray();
|
||||
var migrationOptions = ((IConfigurationManager)host.ConfigurationManager).GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
|
||||
var migrationOptions = host.ConfigurationManager.GetConfiguration<MigrationOptions>(MigrationsListStore.StoreKey);
|
||||
|
||||
if (!host.ConfigurationManager.Configuration.IsStartupWizardCompleted && migrationOptions.Applied.Count == 0)
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
var existingConfigJson = JToken.Parse(File.ReadAllText(oldConfigPath));
|
||||
return _defaultConfigHistory
|
||||
.Select(historicalConfigText => JToken.Parse(historicalConfigText))
|
||||
.Select(JToken.Parse)
|
||||
.Any(historicalConfigJson => JToken.DeepEquals(existingConfigJson, historicalConfigJson));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
if (entry[6].SQLiteType != SQLiteType.Null && !Guid.TryParse(entry[6].ToString(), out guid))
|
||||
{
|
||||
// This is not a valid Guid, see if it is an internal ID from an old Emby schema
|
||||
_logger.LogWarning("Invalid Guid in UserId column: ", entry[6].ToString());
|
||||
_logger.LogWarning("Invalid Guid in UserId column: {Guid}", entry[6].ToString());
|
||||
|
||||
using var statement = userDbConnection.PrepareStatement("SELECT guid FROM LocalUsersv2 WHERE Id=@Id");
|
||||
statement.TryBind("@Id", entry[6].ToString());
|
||||
|
||||
129
Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
Normal file
129
Jellyfin.Server/Migrations/Routines/MigrateAuthenticationDb.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Controller;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Jellyfin.Server.Migrations.Routines
|
||||
{
|
||||
/// <summary>
|
||||
/// A migration that moves data from the authentication database into the new schema.
|
||||
/// </summary>
|
||||
public class MigrateAuthenticationDb : IMigrationRoutine
|
||||
{
|
||||
private const string DbFilename = "authentication.db";
|
||||
|
||||
private readonly ILogger<MigrateAuthenticationDb> _logger;
|
||||
private readonly JellyfinDbProvider _dbProvider;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateAuthenticationDb"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="dbProvider">The database provider.</param>
|
||||
/// <param name="appPaths">The server application paths.</param>
|
||||
public MigrateAuthenticationDb(ILogger<MigrateAuthenticationDb> logger, JellyfinDbProvider dbProvider, IServerApplicationPaths appPaths)
|
||||
{
|
||||
_logger = logger;
|
||||
_dbProvider = dbProvider;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => Guid.Parse("5BD72F41-E6F3-4F60-90AA-09869ABE0E22");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "MigrateAuthenticationDatabase";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool PerformOnNewInstall => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Perform()
|
||||
{
|
||||
var dataPath = _appPaths.DataPath;
|
||||
using (var connection = SQLite3.Open(
|
||||
Path.Combine(dataPath, DbFilename),
|
||||
ConnectionFlags.ReadOnly,
|
||||
null))
|
||||
{
|
||||
using var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
var authenticatedDevices = connection.Query("SELECT * FROM Tokens");
|
||||
|
||||
foreach (var row in authenticatedDevices)
|
||||
{
|
||||
if (row[6].IsDbNull())
|
||||
{
|
||||
dbContext.ApiKeys.Add(new ApiKey(row[3].ToString())
|
||||
{
|
||||
AccessToken = row[1].ToString(),
|
||||
DateCreated = row[9].ToDateTime(),
|
||||
DateLastActivity = row[10].ToDateTime()
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
dbContext.Devices.Add(new Device(
|
||||
new Guid(row[6].ToString()),
|
||||
row[3].ToString(),
|
||||
row[4].ToString(),
|
||||
row[5].ToString(),
|
||||
row[2].ToString())
|
||||
{
|
||||
AccessToken = row[1].ToString(),
|
||||
IsActive = row[8].ToBool(),
|
||||
DateCreated = row[9].ToDateTime(),
|
||||
DateLastActivity = row[10].ToDateTime()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var deviceOptions = connection.Query("SELECT * FROM Devices");
|
||||
var deviceIds = new HashSet<string>();
|
||||
foreach (var row in deviceOptions)
|
||||
{
|
||||
if (row[2].IsDbNull())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var deviceId = row[2].ToString();
|
||||
if (deviceIds.Contains(deviceId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
deviceIds.Add(deviceId);
|
||||
|
||||
dbContext.DeviceOptions.Add(new DeviceOptions(deviceId)
|
||||
{
|
||||
CustomName = row[1].IsDbNull() ? null : row[1].ToString()
|
||||
});
|
||||
}
|
||||
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Move(Path.Combine(dataPath, DbFilename), Path.Combine(dataPath, DbFilename + ".old"));
|
||||
|
||||
var journalPath = Path.Combine(dataPath, DbFilename + "-journal");
|
||||
if (File.Exists(journalPath))
|
||||
{
|
||||
File.Move(journalPath, Path.Combine(dataPath, DbFilename + ".old-journal"));
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_logger.LogError(e, "Error renaming legacy activity log database to 'authentication.db.old'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
@@ -121,7 +120,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
var displayPreferences = new DisplayPreferences(dtoUserId, itemId, client)
|
||||
{
|
||||
IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : (IndexingKind?)null,
|
||||
IndexBy = Enum.TryParse<IndexingKind>(dto.IndexBy, true, out var indexBy) ? indexBy : null,
|
||||
ShowBackdrop = dto.ShowBackdrop,
|
||||
ShowSidebar = dto.ShowSidebar,
|
||||
ScrollDirection = dto.ScrollDirection,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Emby.Server.Implementations.Serialization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Common.Json;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Users;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SQLitePCL.pretty;
|
||||
@@ -27,7 +27,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
private readonly ILogger<MigrateUserDb> _logger;
|
||||
private readonly IServerApplicationPaths _paths;
|
||||
private readonly JellyfinDbProvider _provider;
|
||||
private readonly MyXmlSerializer _xmlSerializer;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MigrateUserDb"/> class.
|
||||
@@ -40,7 +40,7 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
ILogger<MigrateUserDb> logger,
|
||||
IServerApplicationPaths paths,
|
||||
JellyfinDbProvider provider,
|
||||
MyXmlSerializer xmlSerializer)
|
||||
IXmlSerializer xmlSerializer)
|
||||
{
|
||||
_logger = logger;
|
||||
_paths = paths;
|
||||
@@ -82,11 +82,14 @@ namespace Jellyfin.Server.Migrations.Routines
|
||||
|
||||
var userDataDir = Path.Combine(_paths.UserConfigurationDirectoryPath, mockup.Name);
|
||||
|
||||
var config = File.Exists(Path.Combine(userDataDir, "config.xml"))
|
||||
? (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), Path.Combine(userDataDir, "config.xml"))
|
||||
var configPath = Path.Combine(userDataDir, "config.xml");
|
||||
var config = File.Exists(configPath)
|
||||
? (UserConfiguration?)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), configPath) ?? new UserConfiguration()
|
||||
: new UserConfiguration();
|
||||
var policy = File.Exists(Path.Combine(userDataDir, "policy.xml"))
|
||||
? (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), Path.Combine(userDataDir, "policy.xml"))
|
||||
|
||||
var policyPath = Path.Combine(userDataDir, "policy.xml");
|
||||
var policy = File.Exists(policyPath)
|
||||
? (UserPolicy?)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), policyPath) ?? new UserPolicy()
|
||||
: new UserPolicy();
|
||||
policy.AuthenticationProviderId = policy.AuthenticationProviderId?.Replace(
|
||||
"Emby.Server.Implementations.Library",
|
||||
|
||||
@@ -5,18 +5,20 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.ClientEvent;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
@@ -121,11 +123,11 @@ namespace Jellyfin.Server
|
||||
|
||||
// Log uncaught exceptions to the logging instead of std error
|
||||
AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionToConsole;
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, e)
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, e)
|
||||
=> _logger.LogCritical((Exception)e.ExceptionObject, "Unhandled Exception");
|
||||
|
||||
// Intercept Ctrl+C and Ctrl+Break
|
||||
Console.CancelKeyPress += (sender, e) =>
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
@@ -139,7 +141,7 @@ namespace Jellyfin.Server
|
||||
};
|
||||
|
||||
// Register a SIGTERM handler
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, e) =>
|
||||
AppDomain.CurrentDomain.ProcessExit += (_, _) =>
|
||||
{
|
||||
if (_tokenSource.IsCancellationRequested)
|
||||
{
|
||||
@@ -180,7 +182,7 @@ namespace Jellyfin.Server
|
||||
"The server is expected to host the web client, but the provided content directory is either " +
|
||||
$"invalid or empty: {webContentPath}. If you do not want to host the web client with the " +
|
||||
"server, you may set the '--nowebclient' command line flag, or set" +
|
||||
$"'{MediaBrowser.Controller.Extensions.ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
||||
$"'{ConfigurationExtensions.HostWebClientKey}=false' in your config settings.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,9 +197,9 @@ namespace Jellyfin.Server
|
||||
|
||||
try
|
||||
{
|
||||
await webHost.StartAsync().ConfigureAwait(false);
|
||||
await webHost.StartAsync(_tokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex) when (ex is not TaskCanceledException)
|
||||
{
|
||||
_logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in network.xml and try again.");
|
||||
throw;
|
||||
@@ -222,6 +224,14 @@ namespace Jellyfin.Server
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInformation("Running query planner optimizations in the database... This might take a while");
|
||||
// Run before disposing the application
|
||||
using var context = appHost.Resolve<JellyfinDbProvider>().CreateContext();
|
||||
if (context.Database.IsSqlite())
|
||||
{
|
||||
context.Database.ExecuteSqlRaw("PRAGMA optimize");
|
||||
}
|
||||
|
||||
appHost.Dispose();
|
||||
}
|
||||
|
||||
@@ -310,8 +320,8 @@ namespace Jellyfin.Server
|
||||
}
|
||||
}
|
||||
|
||||
// Bind to unix socket (only on macOS and Linux)
|
||||
if (startupConfig.UseUnixSocket() && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
// Bind to unix socket (only on unix systems)
|
||||
if (startupConfig.UseUnixSocket() && Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
{
|
||||
var socketPath = startupConfig.GetUnixSocketPath();
|
||||
if (string.IsNullOrEmpty(socketPath))
|
||||
@@ -396,7 +406,7 @@ namespace Jellyfin.Server
|
||||
{
|
||||
if (options.DataDir != null
|
||||
|| Directory.Exists(Path.Combine(dataDir, "config"))
|
||||
|| RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
|| OperatingSystem.IsWindows())
|
||||
{
|
||||
// Hang config folder off already set dataDir
|
||||
configDir = Path.Combine(dataDir, "config");
|
||||
@@ -434,7 +444,7 @@ namespace Jellyfin.Server
|
||||
|
||||
if (string.IsNullOrEmpty(cacheDir))
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
// Hang cache folder off already set dataDir
|
||||
cacheDir = Path.Combine(dataDir, "cache");
|
||||
@@ -535,11 +545,11 @@ namespace Jellyfin.Server
|
||||
// Get a stream of the resource contents
|
||||
// NOTE: The .csproj name is used instead of the assembly name in the resource path
|
||||
const string ResourcePath = "Jellyfin.Server.Resources.Configuration.logging.json";
|
||||
await using Stream? resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
|
||||
await using Stream resource = typeof(Program).Assembly.GetManifestResourceStream(ResourcePath)
|
||||
?? throw new InvalidOperationException($"Invalid resource path: '{ResourcePath}'");
|
||||
|
||||
// Copy the resource contents to the expected file path for the config file
|
||||
await using Stream dst = File.Open(configPath, FileMode.CreateNew);
|
||||
await using Stream dst = new FileStream(configPath, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||
await resource.CopyToAsync(dst).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -586,15 +596,14 @@ namespace Jellyfin.Server
|
||||
try
|
||||
{
|
||||
// Serilog.Log is used by SerilogLoggerFactory when no logger is specified
|
||||
Serilog.Log.Logger = new LoggerConfiguration()
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Logger(lc =>
|
||||
lc.ReadFrom.Configuration(configuration)
|
||||
.Enrich.FromLogContext()
|
||||
.Enrich.WithThreadId()
|
||||
.Filter.ByExcluding(Matching.FromSource<ClientEventLogger>()))
|
||||
.WriteTo.Logger(lc =>
|
||||
lc
|
||||
.WriteTo.Map(
|
||||
lc.WriteTo.Map(
|
||||
"ClientName",
|
||||
(clientName, wt)
|
||||
=> wt.File(
|
||||
@@ -607,7 +616,7 @@ namespace Jellyfin.Server
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger = new LoggerConfiguration()
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Logger(lc =>
|
||||
lc.WriteTo.Async(x => x.File(
|
||||
Path.Combine(appPaths.LogDirectoryPath, "log_.log"),
|
||||
@@ -630,7 +639,7 @@ namespace Jellyfin.Server
|
||||
.Filter.ByIncludingOnly(Matching.FromSource<ClientEventLogger>()))
|
||||
.CreateLogger();
|
||||
|
||||
Serilog.Log.Logger.Fatal(ex, "Failed to create/read logger configuration");
|
||||
Log.Logger.Fatal(ex, "Failed to create/read logger configuration");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Text;
|
||||
using Jellyfin.Networking.Configuration;
|
||||
using Jellyfin.Server.Extensions;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Infrastructure;
|
||||
using Jellyfin.Server.Middleware;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -14,6 +15,8 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -56,6 +59,9 @@ namespace Jellyfin.Server
|
||||
{
|
||||
options.HttpsPort = _serverApplicationHost.HttpsPort;
|
||||
});
|
||||
|
||||
// TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371
|
||||
services.AddSingleton<IActionResultExecutor<PhysicalFileResult>, SymlinkFollowingPhysicalFileResultExecutor>();
|
||||
services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
|
||||
|
||||
services.AddJellyfinApiSwagger();
|
||||
@@ -160,6 +166,7 @@ namespace Jellyfin.Server
|
||||
|
||||
mainApp.UseAuthentication();
|
||||
mainApp.UseJellyfinApiSwagger(_serverConfigurationManager);
|
||||
mainApp.UseQueryStringDecoding();
|
||||
mainApp.UseRouting();
|
||||
mainApp.UseAuthorization();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user