using System; using System.Globalization; using System.Net; 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.Networking.HappyEyeballs; using Jellyfin.Server.Extensions; using Jellyfin.Server.HealthChecks; using Jellyfin.Server.Implementations; using Jellyfin.Server.Implementations.Extensions; using Jellyfin.Server.Infrastructure; using MediaBrowser.Common.Net; using MediaBrowser.Controller; 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; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; using Microsoft.VisualBasic; using Prometheus; namespace Jellyfin.Server { /// /// Startup configuration for the Kestrel webhost. /// public class Startup { private readonly IServerApplicationHost _serverApplicationHost; private readonly IServerConfigurationManager _serverConfigurationManager; /// /// Initializes a new instance of the class. /// /// The server application host. public Startup(CoreAppHost appHost) { _serverApplicationHost = appHost; _serverConfigurationManager = appHost.ConfigurationManager; } /// /// Configures the service collection for the webhost. /// /// The service collection. public void ConfigureServices(IServiceCollection services) { services.AddResponseCompression(); services.AddHttpContextAccessor(); services.AddHttpsRedirection(options => { options.HttpsPort = _serverApplicationHost.HttpsPort; }); // TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371 services.AddSingleton, SymlinkFollowingPhysicalFileResultExecutor>(); services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration()); services.AddJellyfinDbContext(); services.AddJellyfinApiSwagger(); // configure custom legacy authentication services.AddCustomAuthentication(); services.AddJellyfinApiAuthorization(); var productHeader = new ProductInfoHeaderValue( _serverApplicationHost.Name.Replace(' ', '-'), _serverApplicationHost.ApplicationVersionString); var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0); var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9); var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8); Func eyeballsHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8, ConnectCallback = HttpClientExtension.OnConnect }; Func defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler() { AutomaticDecompression = DecompressionMethods.All, RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8 }; services.AddHttpClient(NamedClient.Default, c => { c.DefaultRequestHeaders.UserAgent.Add(productHeader); c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.MusicBrainz, c => { c.DefaultRequestHeaders.UserAgent.Add(productHeader); c.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({_serverApplicationHost.ApplicationUserAgentAddress})")); c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) .ConfigurePrimaryHttpMessageHandler(eyeballsHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.DirectIp, c => { c.DefaultRequestHeaders.UserAgent.Add(productHeader); c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader); c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader); c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader); }) .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); services.AddHttpClient(NamedClient.Dlna, c => { c.DefaultRequestHeaders.UserAgent.ParseAdd( string.Format( CultureInfo.InvariantCulture, "{0}/{1} UPnP/1.0 {2}/{3}", Environment.OSVersion.Platform, Environment.OSVersion, _serverApplicationHost.Name, _serverApplicationHost.ApplicationVersionString)); c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", _serverApplicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0 c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", _serverApplicationHost.FriendlyName); // REVIEW: where does this come from? }) .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate); services.AddHealthChecks() .AddCheck>(nameof(JellyfinDbContext)); services.AddHlsPlaylistGenerator(); } /// /// Configures the app builder for the webhost. /// /// The application builder. /// The webhost environment. /// The application config. public void Configure( IApplicationBuilder app, IWebHostEnvironment env, IConfiguration appConfig) { app.UseBaseUrlRedirection(); // Wrap rest of configuration so everything only listens on BaseUrl. var config = _serverConfigurationManager.GetNetworkConfiguration(); app.Map(config.BaseUrl, mainApp => { if (env.IsDevelopment()) { mainApp.UseDeveloperExceptionPage(); } mainApp.UseForwardedHeaders(); mainApp.UseMiddleware(); mainApp.UseMiddleware(); mainApp.UseWebSockets(); mainApp.UseResponseCompression(); mainApp.UseCors(); if (config.RequireHttps && _serverApplicationHost.ListenWithHttps) { mainApp.UseHttpsRedirection(); } // This must be injected before any path related middleware. mainApp.UsePathTrim(); if (appConfig.HostWebClient()) { var extensionProvider = new FileExtensionContentTypeProvider(); // subtitles octopus requires .data, .mem files. extensionProvider.Mappings.Add(".data", MediaTypeNames.Application.Octet); extensionProvider.Mappings.Add(".mem", MediaTypeNames.Application.Octet); mainApp.UseDefaultFiles(new DefaultFilesOptions { FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), RequestPath = "/web" }); mainApp.UseStaticFiles(new StaticFileOptions { FileProvider = new PhysicalFileProvider(_serverConfigurationManager.ApplicationPaths.WebPath), RequestPath = "/web", ContentTypeProvider = extensionProvider }); mainApp.UseRobotsRedirection(); } mainApp.UseStaticFiles(); mainApp.UseAuthentication(); mainApp.UseJellyfinApiSwagger(_serverConfigurationManager); mainApp.UseQueryStringDecoding(); mainApp.UseRouting(); mainApp.UseAuthorization(); mainApp.UseLanFiltering(); mainApp.UseIpBasedAccessValidation(); mainApp.UseWebSocketHandler(); mainApp.UseServerStartupMessage(); if (_serverConfigurationManager.Configuration.EnableMetrics) { // Must be registered after any middleware that could change HTTP response codes or the data will be bad mainApp.UseHttpMetrics(); } mainApp.UseEndpoints(endpoints => { endpoints.MapControllers(); if (_serverConfigurationManager.Configuration.EnableMetrics) { endpoints.MapMetrics(); } endpoints.MapHealthChecks("/health"); }); }); } } }