aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server')
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs53
-rw-r--r--Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs4
-rw-r--r--Jellyfin.Server/Formatters/CssOutputFormatter.cs36
-rw-r--r--Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs4
-rw-r--r--Jellyfin.Server/Middleware/ExceptionMiddleware.cs14
-rw-r--r--Jellyfin.Server/Models/JsonOptions.cs41
-rw-r--r--Jellyfin.Server/Models/ServerCorsPolicy.cs30
-rw-r--r--Jellyfin.Server/Startup.cs4
8 files changed, 134 insertions, 52 deletions
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index a6817421a..009746243 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Text.Json.Serialization;
using Jellyfin.Api;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
@@ -11,6 +10,9 @@ using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
using Jellyfin.Server.Formatters;
+using Jellyfin.Server.Models;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
@@ -71,11 +73,18 @@ namespace Jellyfin.Server.Extensions
/// <returns>The MVC builder.</returns>
public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl)
{
- return serviceCollection.AddMvc(opts =>
+ return serviceCollection
+ .AddCors(options =>
+ {
+ options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, ServerCorsPolicy.DefaultPolicy);
+ })
+ .AddMvc(opts =>
{
opts.UseGeneralRoutePrefix(baseUrl);
opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
+
+ opts.OutputFormatters.Add(new CssOutputFormatter());
})
// Clear app parts to avoid other assemblies being picked up
@@ -83,8 +92,20 @@ namespace Jellyfin.Server.Extensions
.AddApplicationPart(typeof(StartupController).Assembly)
.AddJsonOptions(options =>
{
- // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON.
- options.JsonSerializerOptions.PropertyNamingPolicy = null;
+ // Update all properties that are set in JsonDefaults
+ var jsonOptions = JsonDefaults.PascalCase;
+
+ // From JsonDefaults
+ options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
+ options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
+ options.JsonSerializerOptions.Converters.Clear();
+ foreach (var converter in jsonOptions.Converters)
+ {
+ options.JsonSerializerOptions.Converters.Add(converter);
+ }
+
+ // From JsonDefaults.PascalCase
+ options.JsonSerializerOptions.PropertyNamingPolicy = jsonOptions.PropertyNamingPolicy;
})
.AddControllersAsServices();
}
@@ -118,6 +139,7 @@ namespace Jellyfin.Server.Extensions
{
{ securitySchemeRef, Array.Empty<string>() }
});
+ c.SwaggerDoc("api-docs", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" });
// Add all xml doc files to swagger generator.
var xmlFiles = Directory.GetFiles(
@@ -137,7 +159,30 @@ namespace Jellyfin.Server.Extensions
// Use method name as operationId
c.CustomOperationIds(description =>
description.TryGetMethodInfo(out MethodInfo methodInfo) ? methodInfo.Name : null);
+
+ // TODO - remove when all types are supported in System.Text.Json
+ c.AddSwaggerTypeMappings();
});
}
+
+ private static void AddSwaggerTypeMappings(this SwaggerGenOptions options)
+ {
+ /*
+ * TODO remove when System.Text.Json supports non-string keys.
+ * Used in Jellyfin.Api.Controller.GetChannels.
+ */
+ options.MapType<Dictionary<ImageType, string>>(() =>
+ new OpenApiSchema
+ {
+ Type = "object",
+ Properties = typeof(ImageType).GetEnumNames().ToDictionary(
+ name => name,
+ name => new OpenApiSchema
+ {
+ Type = "string",
+ Format = "string"
+ })
+ });
+ }
}
}
diff --git a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
index e6ad6dfb1..989c8ecea 100644
--- a/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
+++ b/Jellyfin.Server/Formatters/CamelCaseJsonProfileFormatter.cs
@@ -1,4 +1,4 @@
-using Jellyfin.Server.Models;
+using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
/// <summary>
/// Initializes a new instance of the <see cref="CamelCaseJsonProfileFormatter"/> class.
/// </summary>
- public CamelCaseJsonProfileFormatter() : base(JsonOptions.CamelCase)
+ public CamelCaseJsonProfileFormatter() : base(JsonDefaults.CamelCase)
{
SupportedMediaTypes.Clear();
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json;profile=\"CamelCase\""));
diff --git a/Jellyfin.Server/Formatters/CssOutputFormatter.cs b/Jellyfin.Server/Formatters/CssOutputFormatter.cs
new file mode 100644
index 000000000..b3771b7fe
--- /dev/null
+++ b/Jellyfin.Server/Formatters/CssOutputFormatter.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Jellyfin.Server.Formatters
+{
+ /// <summary>
+ /// Css output formatter.
+ /// </summary>
+ public class CssOutputFormatter : TextOutputFormatter
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CssOutputFormatter"/> class.
+ /// </summary>
+ public CssOutputFormatter()
+ {
+ SupportedMediaTypes.Add("text/css");
+
+ SupportedEncodings.Add(Encoding.UTF8);
+ SupportedEncodings.Add(Encoding.Unicode);
+ }
+
+ /// <summary>
+ /// Write context object to stream.
+ /// </summary>
+ /// <param name="context">Writer context.</param>
+ /// <param name="selectedEncoding">Unused. Writer encoding.</param>
+ /// <returns>Write stream task.</returns>
+ public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding)
+ {
+ return context.HttpContext.Response.WriteAsync(context.Object?.ToString());
+ }
+ }
+}
diff --git a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
index 675f4c79e..69963b3fb 100644
--- a/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
+++ b/Jellyfin.Server/Formatters/PascalCaseJsonProfileFormatter.cs
@@ -1,4 +1,4 @@
-using Jellyfin.Server.Models;
+using MediaBrowser.Common.Json;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Net.Http.Headers;
@@ -12,7 +12,7 @@ namespace Jellyfin.Server.Formatters
/// <summary>
/// Initializes a new instance of the <see cref="PascalCaseJsonProfileFormatter"/> class.
/// </summary>
- public PascalCaseJsonProfileFormatter() : base(JsonOptions.PascalCase)
+ public PascalCaseJsonProfileFormatter() : base(JsonDefaults.PascalCase)
{
SupportedMediaTypes.Clear();
// Add application/json for default formatter
diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
index 0d79bbfaf..63effafc1 100644
--- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
@@ -7,7 +7,9 @@ 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
@@ -20,6 +22,7 @@ namespace Jellyfin.Server.Middleware
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
private readonly IServerConfigurationManager _configuration;
+ private readonly IWebHostEnvironment _hostEnvironment;
/// <summary>
/// Initializes a new instance of the <see cref="ExceptionMiddleware"/> class.
@@ -27,14 +30,17 @@ namespace Jellyfin.Server.Middleware
/// <param name="next">Next request delegate.</param>
/// <param name="logger">Instance of the <see cref="ILogger{ExceptionMiddleware}"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ /// <param name="hostEnvironment">Instance of the <see cref="IWebHostEnvironment"/> interface.</param>
public ExceptionMiddleware(
RequestDelegate next,
ILogger<ExceptionMiddleware> logger,
- IServerConfigurationManager serverConfigurationManager)
+ IServerConfigurationManager serverConfigurationManager,
+ IWebHostEnvironment hostEnvironment)
{
_next = next;
_logger = logger;
_configuration = serverConfigurationManager;
+ _hostEnvironment = hostEnvironment;
}
/// <summary>
@@ -85,7 +91,11 @@ namespace Jellyfin.Server.Middleware
context.Response.StatusCode = GetStatusCode(ex);
context.Response.ContentType = MediaTypeNames.Text.Plain;
- var errorContent = NormalizeExceptionMessage(ex.Message);
+
+ // 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);
}
}
diff --git a/Jellyfin.Server/Models/JsonOptions.cs b/Jellyfin.Server/Models/JsonOptions.cs
deleted file mode 100644
index 2f0df3d2c..000000000
--- a/Jellyfin.Server/Models/JsonOptions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System.Text.Json;
-
-namespace Jellyfin.Server.Models
-{
- /// <summary>
- /// Json Options.
- /// </summary>
- public static class JsonOptions
- {
- /// <summary>
- /// Gets CamelCase json options.
- /// </summary>
- public static JsonSerializerOptions CamelCase
- {
- get
- {
- var options = DefaultJsonOptions;
- options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
- return options;
- }
- }
-
- /// <summary>
- /// Gets PascalCase json options.
- /// </summary>
- public static JsonSerializerOptions PascalCase
- {
- get
- {
- var options = DefaultJsonOptions;
- options.PropertyNamingPolicy = null;
- return options;
- }
- }
-
- /// <summary>
- /// Gets base Json Serializer Options.
- /// </summary>
- private static JsonSerializerOptions DefaultJsonOptions => new JsonSerializerOptions();
- }
-}
diff --git a/Jellyfin.Server/Models/ServerCorsPolicy.cs b/Jellyfin.Server/Models/ServerCorsPolicy.cs
new file mode 100644
index 000000000..ae010c042
--- /dev/null
+++ b/Jellyfin.Server/Models/ServerCorsPolicy.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.Cors.Infrastructure;
+
+namespace Jellyfin.Server.Models
+{
+ /// <summary>
+ /// Server Cors Policy.
+ /// </summary>
+ public static class ServerCorsPolicy
+ {
+ /// <summary>
+ /// Default policy name.
+ /// </summary>
+ public const string DefaultPolicyName = "DefaultCorsPolicy";
+
+ /// <summary>
+ /// Default Policy. Allow Everything.
+ /// </summary>
+ public static readonly CorsPolicy DefaultPolicy = new CorsPolicy
+ {
+ // Allow any origin
+ Origins = { "*" },
+
+ // Allow any method
+ Methods = { "*" },
+
+ // Allow any header
+ Headers = { "*" }
+ };
+ }
+} \ No newline at end of file
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 7c49afbfc..bd2887e4a 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -1,5 +1,6 @@
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Middleware;
+using Jellyfin.Server.Models;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Builder;
@@ -68,9 +69,10 @@ namespace Jellyfin.Server
// TODO app.UseMiddleware<WebSocketMiddleware>();
app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync);
- // TODO use when old API is removed: app.UseAuthentication();
+ app.UseAuthentication();
app.UseJellyfinApiSwagger(_serverConfigurationManager);
app.UseRouting();
+ app.UseCors(ServerCorsPolicy.DefaultPolicyName);
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{