pull/12011/merge
Tim Eisele 3 weeks ago committed by GitHub
commit 5de7c384cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -50,6 +50,8 @@
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="MimeTypes" Version="2.5.2" />
<PackageVersion Include="Mime-Detective" Version="23.10.1" />
<PackageVersion Include="Mime-Detective.Definitions.Exhaustive" Version="23.10.1" />
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NEbml" Version="0.12.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />

@ -564,6 +564,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<ITranscodeManager, TranscodeManager>();
serviceCollection.AddScoped<MediaInfoHelper>();
serviceCollection.AddScoped<AudioHelper>();
serviceCollection.AddScoped<UploadHelper>();
serviceCollection.AddScoped<DynamicHlsHelper>();
serviceCollection.AddScoped<IClientEventLogger, ClientEventLogger>();
serviceCollection.AddSingleton<IDirectoryService, DirectoryService>();

@ -49,6 +49,7 @@ public class ImageController : BaseJellyfinApiController
private readonly ILogger<ImageController> _logger;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IApplicationPaths _appPaths;
private readonly UploadHelper _uploadHelper;
/// <summary>
/// Initializes a new instance of the <see cref="ImageController"/> class.
@ -61,6 +62,7 @@ public class ImageController : BaseJellyfinApiController
/// <param name="logger">Instance of the <see cref="ILogger{ImageController}"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
/// <param name="uploadHelper">The <see cref="UploadHelper"/> instance.</param>
public ImageController(
IUserManager userManager,
ILibraryManager libraryManager,
@ -69,7 +71,8 @@ public class ImageController : BaseJellyfinApiController
IFileSystem fileSystem,
ILogger<ImageController> logger,
IServerConfigurationManager serverConfigurationManager,
IApplicationPaths appPaths)
IApplicationPaths appPaths,
UploadHelper uploadHelper)
{
_userManager = userManager;
_libraryManager = libraryManager;
@ -79,6 +82,7 @@ public class ImageController : BaseJellyfinApiController
_logger = logger;
_serverConfigurationManager = serverConfigurationManager;
_appPaths = appPaths;
_uploadHelper = uploadHelper;
}
private static CryptoStream GetFromBase64Stream(Stream inputStream)
@ -1799,26 +1803,22 @@ public class ImageController : BaseJellyfinApiController
[AcceptsImageFile]
public async Task<ActionResult> UploadCustomSplashscreen()
{
if (!TryGetImageExtensionFromContentType(Request.ContentType, out var extension))
{
return BadRequest("Incorrect ContentType.");
}
var stream = GetFromBase64Stream(Request.Body);
await using (stream.ConfigureAwait(false))
{
var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension);
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
brandingOptions.SplashscreenLocation = filePath;
_serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await using (fs.ConfigureAwait(false))
var mimeInfo = _uploadHelper.GetMimeInfo(stream, Request.ContentType);
if (mimeInfo is not null)
{
await stream.CopyToAsync(fs, CancellationToken.None).ConfigureAwait(false);
var filePathWithExtension = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + mimeInfo.Definition.File.Extensions.First());
UploadHelper.WriteStreamToFile(stream, filePathWithExtension, CancellationToken.None);
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
brandingOptions.SplashscreenLocation = filePathWithExtension;
_serverConfigurationManager.SaveConfiguration("branding", brandingOptions);
return NoContent();
}
return NoContent();
return BadRequest("Incorrect ContentType.");
}
}

@ -0,0 +1,124 @@
using System;
using System.Collections.Frozen;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Common;
using MediaBrowser.Model.IO;
using MimeDetective;
using MimeDetective.Definitions;
using MimeDetective.Definitions.Licensing;
using MimeDetective.Engine;
using MimeDetective.Storage;
namespace Jellyfin.Api.Helpers;
/// <summary>
/// Utitlity class providing upload helper functions.
/// </summary>
public class UploadHelper
{
private readonly FrozenSet<Definition> _videoDefinitions;
private readonly FrozenSet<Definition> _audioDefinitions;
private readonly FrozenSet<Definition> _imageDefinitions;
/// <summary>
/// Initializes a new instance of the <see cref="UploadHelper"/> class.
/// </summary>
/// <param name="namingOptions">The naming options.</param>
public UploadHelper(
NamingOptions namingOptions)
{
var allDefinitions = new ExhaustiveBuilder()
{
UsageType = UsageType.PersonalNonCommercial
}.Build();
var extensions = namingOptions.AudioFileExtensions.Select(x => x.Replace(".", string.Empty, StringComparison.OrdinalIgnoreCase)).ToArray();
_audioDefinitions = allDefinitions
.ScopeExtensions(extensions)
.TrimMeta()
.TrimDescription()
.ToFrozenSet();
extensions = namingOptions.VideoFileExtensions.Select(x => x.Replace(".", string.Empty, StringComparison.OrdinalIgnoreCase)).ToArray();
_videoDefinitions = allDefinitions
.ScopeExtensions(extensions)
.TrimMeta()
.TrimDescription()
.ToFrozenSet();
extensions =
[
"jpg",
"png",
"gif",
"webp",
"bmp"
];
_imageDefinitions = allDefinitions
.ScopeExtensions(extensions)
.TrimMeta()
.TrimDescription()
.ToFrozenSet();
}
/// <summary>
/// Checks if data MIME type matches content type and returns the MIME type information.
/// </summary>
/// <param name="stream">The data stream.</param>
/// <param name="contentType">The content type.</param>
/// <returns>MIME type information.</returns>
public DefinitionMatch? GetMimeInfo(Stream? stream, string? contentType)
{
ArgumentNullException.ThrowIfNull(stream);
ArgumentNullException.ThrowIfNull(contentType);
var definitions = GetDefinitionsForType(contentType.Split('/')[0]);
var inspector = new ContentInspectorBuilder()
{
Definitions = definitions.ToList(),
}.Build();
var realMimeTypeMatchesContentType = inspector.Inspect(stream)
.Where(r => string.Equals(r.Definition.File.MimeType, contentType, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(r => r.Points)
.FirstOrDefault(r => r.Type == DefinitionMatchType.Complete);
if (realMimeTypeMatchesContentType is not null)
{
return realMimeTypeMatchesContentType;
}
return null;
}
/// <summary>
/// Writes the stream content to a file.
/// </summary>
/// <param name="stream">The data stream.</param>
/// <param name="filePath">The file path to write the data to.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public static async void WriteStreamToFile(Stream stream, string filePath, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(stream);
ArgumentNullException.ThrowIfNull(filePath);
var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await using (fs.ConfigureAwait(false))
{
await stream.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
}
private FrozenSet<Definition> GetDefinitionsForType(string type)
{
return type switch
{
"audio" => _audioDefinitions,
"video" => _videoDefinitions,
"image" => _imageDefinitions,
_ => FrozenSet<Definition>.Empty,
};
}
}

@ -13,6 +13,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" />
<PackageReference Include="Microsoft.Extensions.Http" />
<PackageReference Include="Mime-Detective" />
<PackageReference Include="Mime-Detective.Definitions.Exhaustive" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" />
</ItemGroup>

Loading…
Cancel
Save