aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations')
-rw-r--r--Emby.Server.Implementations/Activity/ActivityManager.cs7
-rw-r--r--Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs6
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/AppBase/ConfigurationHelper.cs4
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs309
-rw-r--r--Emby.Server.Implementations/Archiving/ZipClient.cs6
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs11
-rw-r--r--Emby.Server.Implementations/Channels/ChannelPostScanTask.cs48
-rw-r--r--Emby.Server.Implementations/Collections/CollectionImageProvider.cs12
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs4
-rw-r--r--Emby.Server.Implementations/ConfigurationOptions.cs13
-rw-r--r--Emby.Server.Implementations/Cryptography/CryptographyProvider.cs129
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs27
-rw-r--r--Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs11
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs286
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs4
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserRepository.cs34
-rw-r--r--Emby.Server.Implementations/Devices/DeviceId.cs12
-rw-r--r--Emby.Server.Implementations/Devices/DeviceManager.cs12
-rw-r--r--Emby.Server.Implementations/Diagnostics/CommonProcess.cs2
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs27
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj10
-rw-r--r--Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs24
-rw-r--r--Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs17
-rw-r--r--Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs141
-rw-r--r--Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs23
-rw-r--r--Emby.Server.Implementations/HttpServer/FileWriter.cs46
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpListenerHost.cs175
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs106
-rw-r--r--Emby.Server.Implementations/HttpServer/IHttpListener.cs17
-rw-r--r--Emby.Server.Implementations/HttpServer/LoggerUtils.cs55
-rw-r--r--Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs9
-rw-r--r--Emby.Server.Implementations/HttpServer/ResponseFilter.cs10
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs3
-rw-r--r--Emby.Server.Implementations/HttpServer/StreamWriter.cs21
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs39
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs10
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs21
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs64
-rw-r--r--Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs58
-rw-r--r--Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs14
-rw-r--r--Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs141
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs75
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs5
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs3
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs8
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs4
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs46
-rw-r--r--Emby.Server.Implementations/Library/Validators/PeopleValidator.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs15
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs29
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs13
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs4
-rw-r--r--Emby.Server.Implementations/Localization/LocalizationManager.cs152
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/au.csv8
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/be.csv6
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/de.csv10
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/kz.csv13
-rw-r--r--Emby.Server.Implementations/Localization/Ratings/ru.csv5
-rw-r--r--Emby.Server.Implementations/MediaEncoder/EncodingManager.cs4
-rw-r--r--Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs39
-rw-r--r--Emby.Server.Implementations/Net/IWebSocket.cs5
-rw-r--r--Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs4
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs72
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs13
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs2
-rw-r--r--Emby.Server.Implementations/Security/EncryptionManager.cs57
-rw-r--r--Emby.Server.Implementations/Serialization/JsonSerializer.cs21
-rw-r--r--Emby.Server.Implementations/Services/HttpResult.cs7
-rw-r--r--Emby.Server.Implementations/Services/ResponseHelper.cs7
-rw-r--r--Emby.Server.Implementations/Services/ServiceExec.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServiceHandler.cs2
-rw-r--r--Emby.Server.Implementations/Services/ServicePath.cs161
-rw-r--r--Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs52
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs2
-rw-r--r--Emby.Server.Implementations/Session/SessionWebSocketListener.cs3
-rw-r--r--Emby.Server.Implementations/SocketSharp/HttpFile.cs18
-rw-r--r--Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs204
-rw-r--r--Emby.Server.Implementations/SocketSharp/RequestMono.cs659
-rw-r--r--Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs105
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs136
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs518
-rw-r--r--Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs98
-rw-r--r--Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs9
-rw-r--r--Emby.Server.Implementations/UserViews/DynamicImageProvider.cs13
-rw-r--r--Emby.Server.Implementations/UserViews/FolderImageProvider.cs4
-rw-r--r--Emby.Server.Implementations/WebSockets/WebSocketHandler.cs10
-rw-r--r--Emby.Server.Implementations/WebSockets/WebSocketManager.cs102
96 files changed, 3072 insertions, 1671 deletions
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
index 6febcc2f7..0c513ea12 100644
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ b/Emby.Server.Implementations/Activity/ActivityManager.cs
@@ -39,8 +39,13 @@ namespace Emby.Server.Implementations.Activity
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
- foreach (var item in result.Items.Where(i => !i.UserId.Equals(Guid.Empty)))
+ foreach (var item in result.Items)
{
+ if (item.UserId == Guid.Empty)
+ {
+ continue;
+ }
+
var user = _userManager.GetUserById(item.UserId);
if (user != null)
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index f26cc4f62..65cdccfa5 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -73,12 +73,6 @@ namespace Emby.Server.Implementations.AppBase
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
/// <summary>
- /// Gets the path to where temporary update files will be stored
- /// </summary>
- /// <value>The plugin configurations path.</value>
- public string TempUpdatePath => Path.Combine(ProgramDataPath, "updates");
-
- /// <summary>
/// Gets the path to the log directory
/// </summary>
/// <value>The log directory path.</value>
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 5feac1adf..af60a8dce 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.AppBase
get
{
// Lazy load
- LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer, FileSystem));
+ LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationLoaded, ref _configurationSyncLock, () => (BaseApplicationConfiguration)ConfigurationHelper.GetXmlConfiguration(ConfigurationType, CommonApplicationPaths.SystemConfigurationFilePath, XmlSerializer));
return _configuration;
}
protected set
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 3faad76e7..90b97061f 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
namespace Emby.Server.Implementations.AppBase
@@ -18,9 +17,8 @@ namespace Emby.Server.Implementations.AppBase
/// <param name="type">The type.</param>
/// <param name="path">The path.</param>
/// <param name="xmlSerializer">The XML serializer.</param>
- /// <param name="fileSystem">The file system</param>
/// <returns>System.Object.</returns>
- public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer, IFileSystem fileSystem)
+ public static object GetXmlConfiguration(Type type, string path, IXmlSerializer xmlSerializer)
{
object configuration;
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 9d4fd95a0..ae5460674 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -28,13 +28,13 @@ using Emby.Server.Implementations.Data;
using Emby.Server.Implementations.Devices;
using Emby.Server.Implementations.Diagnostics;
using Emby.Server.Implementations.Dto;
-using Emby.Server.Implementations.FFMpeg;
using Emby.Server.Implementations.HttpServer;
using Emby.Server.Implementations.HttpServer.Security;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library;
using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization;
+using Emby.Server.Implementations.Middleware;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Reflection;
@@ -42,6 +42,7 @@ using Emby.Server.Implementations.ScheduledTasks;
using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Session;
+using Emby.Server.Implementations.SocketSharp;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
using Emby.Server.Implementations.Xml;
@@ -102,13 +103,18 @@ using MediaBrowser.Model.Xml;
using MediaBrowser.Providers.Chapters;
using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Subtitles;
+using MediaBrowser.Providers.TV.TheTVDB;
using MediaBrowser.WebDashboard.Api;
using MediaBrowser.XbmcMetadata.Providers;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using ServiceStack;
-using ServiceStack.Text.Jsv;
-using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate;
namespace Emby.Server.Implementations
{
@@ -123,12 +129,6 @@ namespace Emby.Server.Implementations
/// <value><c>true</c> if this instance can self restart; otherwise, <c>false</c>.</value>
public abstract bool CanSelfRestart { get; }
- /// <summary>
- /// Gets or sets a value indicating whether this instance can self update.
- /// </summary>
- /// <value><c>true</c> if this instance can self update; otherwise, <c>false</c>.</value>
- public virtual bool CanSelfUpdate => false;
-
public virtual bool CanLaunchWebBrowser
{
get
@@ -318,6 +318,8 @@ namespace Emby.Server.Implementations
private IMediaSourceManager MediaSourceManager { get; set; }
private IPlaylistManager PlaylistManager { get; set; }
+ private readonly IConfiguration _configuration;
+
/// <summary>
/// Gets or sets the installation manager.
/// </summary>
@@ -356,8 +358,10 @@ namespace Emby.Server.Implementations
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo,
IImageEncoder imageEncoder,
- INetworkManager networkManager)
+ INetworkManager networkManager,
+ IConfiguration configuration)
{
+ _configuration = configuration;
// hack alert, until common can target .net core
BaseExtensions.CryptographyProvider = CryptographyProvider;
@@ -433,7 +437,7 @@ namespace Emby.Server.Implementations
{
if (_deviceId == null)
{
- _deviceId = new DeviceId(ApplicationPaths, LoggerFactory, FileSystemManager);
+ _deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
}
return _deviceId.Value;
@@ -471,7 +475,7 @@ namespace Emby.Server.Implementations
{
try
{
- Logger.LogWarning("Creating instance of {Type}", type);
+ Logger.LogDebug("Creating instance of {Type}", type);
return ActivatorUtilities.CreateInstance(_serviceProvider, type);
}
catch (Exception ex)
@@ -536,7 +540,7 @@ namespace Emby.Server.Implementations
ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
- MediaEncoder.Init();
+ MediaEncoder.SetFFmpegPath();
//if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath))
//{
@@ -551,16 +555,18 @@ namespace Emby.Server.Implementations
var entryPoints = GetExports<IServerEntryPoint>();
- var now = DateTime.UtcNow;
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
await Task.WhenAll(StartEntryPoints(entryPoints, true));
- Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:fff} ms", DateTime.Now - now);
+ Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
Logger.LogInformation("Core startup complete");
HttpServer.GlobalResponse = null;
- now = DateTime.UtcNow;
+ stopWatch.Restart();
await Task.WhenAll(StartEntryPoints(entryPoints, false));
- Logger.LogInformation("Executed all post-startup entry points in {Elapsed:fff} ms", DateTime.Now - now);
+ Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed);
+ stopWatch.Stop();
}
private IEnumerable<Task> StartEntryPoints(IEnumerable<IServerEntryPoint> entryPoints, bool isBeforeStartup)
@@ -611,6 +617,68 @@ namespace Emby.Server.Implementations
await RegisterResources(serviceCollection);
FindParts();
+
+ string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath;
+ if (string.IsNullOrEmpty(contentRoot))
+ {
+ contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src");
+ }
+
+ var host = new WebHostBuilder()
+ .UseKestrel(options =>
+ {
+ options.ListenAnyIP(HttpPort);
+
+ if (EnableHttps && Certificate != null)
+ {
+ options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); });
+ }
+ })
+ .UseContentRoot(contentRoot)
+ .ConfigureServices(services =>
+ {
+ services.AddResponseCompression();
+ services.AddHttpContextAccessor();
+ })
+ .Configure(app =>
+ {
+ app.UseWebSockets();
+
+ app.UseResponseCompression();
+ // TODO app.UseMiddleware<WebSocketMiddleware>();
+ app.Use(ExecuteWebsocketHandlerAsync);
+ app.Use(ExecuteHttpHandlerAsync);
+ })
+ .Build();
+
+ await host.StartAsync();
+ }
+
+ private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func<Task> next)
+ {
+ if (!context.WebSockets.IsWebSocketRequest)
+ {
+ await next().ConfigureAwait(false);
+ return;
+ }
+
+ await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false);
+ }
+
+ private async Task ExecuteHttpHandlerAsync(HttpContext context, Func<Task> next)
+ {
+ if (context.WebSockets.IsWebSocketRequest)
+ {
+ await next().ConfigureAwait(false);
+ return;
+ }
+
+ var request = context.Request;
+ var response = context.Response;
+ var localPath = context.Request.Path.ToString();
+
+ var req = new WebSocketSharpRequest(request, response, request.Path, Logger);
+ await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false);
}
protected virtual IHttpClient CreateHttpClient()
@@ -625,12 +693,13 @@ namespace Emby.Server.Implementations
/// </summary>
protected async Task RegisterResources(IServiceCollection serviceCollection)
{
+ serviceCollection.AddMemoryCache();
+
serviceCollection.AddSingleton(ConfigurationManager);
serviceCollection.AddSingleton<IApplicationHost>(this);
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
-
serviceCollection.AddSingleton(JsonSerializer);
serviceCollection.AddSingleton(LoggerFactory);
@@ -640,6 +709,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(EnvironmentInfo);
serviceCollection.AddSingleton(FileSystemManager);
+ serviceCollection.AddSingleton<TvDbClientManager>();
HttpClient = CreateHttpClient();
serviceCollection.AddSingleton(HttpClient);
@@ -668,10 +738,10 @@ namespace Emby.Server.Implementations
InstallationManager = new InstallationManager(LoggerFactory, this, ApplicationPaths, HttpClient, JsonSerializer, ServerConfigurationManager, FileSystemManager, CryptographyProvider, ZipClient, PackageRuntime);
serviceCollection.AddSingleton(InstallationManager);
- ZipClient = new ZipClient(FileSystemManager);
+ ZipClient = new ZipClient();
serviceCollection.AddSingleton(ZipClient);
- HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor());
+ HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper);
serviceCollection.AddSingleton(HttpResultFactory);
serviceCollection.AddSingleton<IServerApplicationHost>(this);
@@ -706,7 +776,7 @@ namespace Emby.Server.Implementations
AuthenticationRepository = GetAuthenticationRepository();
serviceCollection.AddSingleton(AuthenticationRepository);
- UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager, CryptographyProvider);
+ UserManager = new UserManager(LoggerFactory, ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, this, JsonSerializer, FileSystemManager);
serviceCollection.AddSingleton(UserManager);
LibraryManager = new LibraryManager(this, LoggerFactory, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager, () => UserViewManager);
@@ -716,7 +786,7 @@ namespace Emby.Server.Implementations
var musicManager = new MusicManager(LibraryManager);
serviceCollection.AddSingleton<IMusicManager>(new MusicManager(LibraryManager));
- LibraryMonitor = new LibraryMonitor(LoggerFactory, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
+ LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo);
serviceCollection.AddSingleton(LibraryMonitor);
serviceCollection.AddSingleton<ISearchEngine>(new SearchEngine(LoggerFactory, LibraryManager, UserManager));
@@ -727,11 +797,11 @@ namespace Emby.Server.Implementations
HttpServer = new HttpListenerHost(this,
LoggerFactory,
ServerConfigurationManager,
- "web/index.html",
+ _configuration,
NetworkManager,
JsonSerializer,
XmlSerializer,
- GetParseFn);
+ CreateHttpListener());
HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading");
serviceCollection.AddSingleton(HttpServer);
@@ -742,10 +812,8 @@ namespace Emby.Server.Implementations
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
serviceCollection.AddSingleton(TVSeriesManager);
- var encryptionManager = new EncryptionManager();
- serviceCollection.AddSingleton<IEncryptionManager>(encryptionManager);
+ DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager);
- DeviceManager = new DeviceManager(AuthenticationRepository, JsonSerializer, LibraryManager, LocalizationManager, UserManager, FileSystemManager, LibraryMonitor, ServerConfigurationManager, LoggerFactory, NetworkManager);
serviceCollection.AddSingleton(DeviceManager);
MediaSourceManager = new MediaSourceManager(ItemRepository, ApplicationPaths, LocalizationManager, UserManager, LibraryManager, LoggerFactory, JsonSerializer, FileSystemManager, UserDataManager, () => MediaEncoder);
@@ -757,7 +825,7 @@ namespace Emby.Server.Implementations
ProviderManager = new ProviderManager(HttpClient, SubtitleManager, ServerConfigurationManager, LibraryMonitor, LoggerFactory, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer);
serviceCollection.AddSingleton(ProviderManager);
- DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
+ DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
serviceCollection.AddSingleton(DtoService);
ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager, HttpClient, ProviderManager);
@@ -767,7 +835,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(SessionManager);
serviceCollection.AddSingleton<IDlnaManager>(
- new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this, assemblyInfo));
+ new DlnaManager(XmlSerializer, FileSystemManager, ApplicationPaths, LoggerFactory, JsonSerializer, this));
CollectionManager = new CollectionManager(LibraryManager, ApplicationPaths, LocalizationManager, FileSystemManager, LibraryMonitor, LoggerFactory, ProviderManager);
serviceCollection.AddSingleton(CollectionManager);
@@ -790,7 +858,18 @@ namespace Emby.Server.Implementations
ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
serviceCollection.AddSingleton(ChapterManager);
- RegisterMediaEncoder(serviceCollection);
+ MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
+ LoggerFactory,
+ JsonSerializer,
+ StartupOptions.FFmpegPath,
+ StartupOptions.FFprobePath,
+ ServerConfigurationManager,
+ FileSystemManager,
+ () => SubtitleEncoder,
+ () => MediaSourceManager,
+ ProcessFactory,
+ 5000);
+ serviceCollection.AddSingleton(MediaEncoder);
EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
serviceCollection.AddSingleton(EncodingManager);
@@ -826,16 +905,6 @@ namespace Emby.Server.Implementations
_serviceProvider = serviceCollection.BuildServiceProvider();
}
- protected virtual IBrotliCompressor CreateBrotliCompressor()
- {
- return null;
- }
-
- private static Func<string, object> GetParseFn(Type propertyType)
- {
- return s => JsvReader.GetParseFn(propertyType)(s);
- }
-
public virtual string PackageRuntime => "netcore";
public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo)
@@ -868,11 +937,9 @@ namespace Emby.Server.Implementations
}
}
- protected virtual bool SupportsDualModeSockets => true;
-
- private X509Certificate GetCertificate(CertificateInfo info)
+ private X509Certificate2 GetCertificate(CertificateInfo info)
{
- var certificateLocation = info == null ? null : info.Path;
+ var certificateLocation = info?.Path;
if (string.IsNullOrWhiteSpace(certificateLocation))
{
@@ -911,85 +978,6 @@ namespace Emby.Server.Implementations
return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
}
- protected virtual FFMpegInstallInfo GetFfmpegInstallInfo()
- {
- var info = new FFMpegInstallInfo();
-
- // Windows builds: http://ffmpeg.zeranoe.com/builds/
- // Linux builds: http://johnvansickle.com/ffmpeg/
- // OS X builds: http://ffmpegmac.net/
- // OS X x64: http://www.evermeet.cx/ffmpeg/
-
- if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux)
- {
- info.FFMpegFilename = "ffmpeg";
- info.FFProbeFilename = "ffprobe";
- info.ArchiveType = "7z";
- info.Version = "20170308";
- }
- else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
- {
- info.FFMpegFilename = "ffmpeg.exe";
- info.FFProbeFilename = "ffprobe.exe";
- info.Version = "20170308";
- info.ArchiveType = "7z";
- }
- else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
- {
- info.FFMpegFilename = "ffmpeg";
- info.FFProbeFilename = "ffprobe";
- info.ArchiveType = "7z";
- info.Version = "20170308";
- }
-
- return info;
- }
-
- protected virtual FFMpegInfo GetFFMpegInfo()
- {
- return new FFMpegLoader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager, GetFfmpegInstallInfo())
- .GetFFMpegInfo(StartupOptions);
- }
-
- /// <summary>
- /// Registers the media encoder.
- /// </summary>
- /// <returns>Task.</returns>
- private void RegisterMediaEncoder(IServiceCollection serviceCollection)
- {
- string encoderPath = null;
- string probePath = null;
-
- var info = GetFFMpegInfo();
-
- encoderPath = info.EncoderPath;
- probePath = info.ProbePath;
- var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase);
-
- var mediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
- LoggerFactory,
- JsonSerializer,
- encoderPath,
- probePath,
- hasExternalEncoder,
- ServerConfigurationManager,
- FileSystemManager,
- LiveTvManager,
- IsoManager,
- LibraryManager,
- ChannelManager,
- SessionManager,
- () => SubtitleEncoder,
- () => MediaSourceManager,
- HttpClient,
- ZipClient,
- ProcessFactory,
- 5000);
-
- MediaEncoder = mediaEncoder;
- serviceCollection.AddSingleton(MediaEncoder);
- }
-
/// <summary>
/// Gets the user repository.
/// </summary>
@@ -1026,7 +1014,7 @@ namespace Emby.Server.Implementations
/// </summary>
private void SetStaticProperties()
{
- ((SqliteItemRepository)ItemRepository).ImageProcessor = ImageProcessor;
+ ItemRepository.ImageProcessor = ImageProcessor;
// For now there's no real way to inject these properly
BaseItem.Logger = LoggerFactory.CreateLogger("BaseItem");
@@ -1068,9 +1056,7 @@ namespace Emby.Server.Implementations
.Where(i => i != null)
.ToArray();
- HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>());
-
- StartServer();
+ HttpServer.Init(GetExports<IService>(false), GetExports<IWebSocketListener>(), GetUrlPrefixes());
LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(),
GetExports<IItemResolver>(),
@@ -1154,15 +1140,12 @@ namespace Emby.Server.Implementations
AllConcreteTypes = GetComposablePartAssemblies()
.SelectMany(x => x.ExportedTypes)
- .Where(type =>
- {
- return type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType;
- })
+ .Where(type => type.IsClass && !type.IsAbstract && !type.IsInterface && !type.IsGenericType)
.ToArray();
}
private CertificateInfo CertificateInfo { get; set; }
- protected X509Certificate Certificate { get; private set; }
+ protected X509Certificate2 Certificate { get; private set; }
private IEnumerable<string> GetUrlPrefixes()
{
@@ -1184,45 +1167,7 @@ namespace Emby.Server.Implementations
});
}
- protected abstract IHttpListener CreateHttpListener();
-
- /// <summary>
- /// Starts the server.
- /// </summary>
- private void StartServer()
- {
- try
- {
- ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener());
- return;
- }
- catch (Exception ex)
- {
- var msg = string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase)
- ? "The http server is unable to start due to a Socket error. This can occasionally happen when the operating system takes longer than usual to release the IP bindings from the previous session. This can take up to five minutes. Please try waiting or rebooting the system."
- : "Error starting Http Server";
-
- Logger.LogError(ex, msg);
-
- if (HttpPort == ServerConfiguration.DefaultHttpPort)
- {
- throw;
- }
- }
-
- HttpPort = ServerConfiguration.DefaultHttpPort;
-
- try
- {
- ((HttpListenerHost)HttpServer).StartServer(GetUrlPrefixes().ToArray(), CreateHttpListener());
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Error starting http server");
-
- throw;
- }
- }
+ protected IHttpListener CreateHttpListener() => new WebSocketSharpListener(Logger);
private CertificateInfo GetCertificateInfo(bool generateCertificate)
{
@@ -1458,7 +1403,6 @@ namespace Emby.Server.Implementations
OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(),
OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName,
CanSelfRestart = CanSelfRestart,
- CanSelfUpdate = CanSelfUpdate,
CanLaunchWebBrowser = CanLaunchWebBrowser,
WanAddress = wanAddress,
HasUpdateAvailable = HasUpdateAvailable,
@@ -1466,7 +1410,7 @@ namespace Emby.Server.Implementations
ServerName = FriendlyName,
LocalAddress = localAddress,
SupportsLibraryMonitor = true,
- EncoderLocationType = MediaEncoder.EncoderLocationType,
+ EncoderLocation = MediaEncoder.EncoderLocation,
SystemArchitecture = EnvironmentInfo.SystemArchitecture,
SystemUpdateLevel = SystemUpdateLevel,
PackageName = StartupOptions.PackageName
@@ -1583,7 +1527,7 @@ namespace Emby.Server.Implementations
if (addresses.Count == 0)
{
- addresses.AddRange(NetworkManager.GetLocalIpAddresses());
+ addresses.AddRange(NetworkManager.GetLocalIpAddresses(ServerConfigurationManager.Configuration.IgnoreVirtualInterfaces));
}
var resultList = new List<IpAddressInfo>();
@@ -1660,7 +1604,7 @@ namespace Emby.Server.Implementations
LogErrorResponseBody = false,
LogErrors = logPing,
LogRequest = logPing,
- TimeoutMs = 30000,
+ TimeoutMs = 5000,
BufferContent = false,
CancellationToken = cancellationToken
@@ -1758,21 +1702,6 @@ namespace Emby.Server.Implementations
}
/// <summary>
- /// Updates the application.
- /// </summary>
- /// <param name="package">The package that contains the update</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <param name="progress">The progress.</param>
- public async Task UpdateApplication(PackageVersionInfo package, CancellationToken cancellationToken, IProgress<double> progress)
- {
- await InstallationManager.InstallPackage(package, false, progress, cancellationToken).ConfigureAwait(false);
-
- HasUpdateAvailable = false;
-
- OnApplicationUpdated(package);
- }
-
- /// <summary>
/// This returns localhost in the case of no external dns, and the hostname if the
/// dns is prefixed with a valid Uri prefix.
/// </summary>
diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs
index 1135cf694..6b0fd2dc6 100644
--- a/Emby.Server.Implementations/Archiving/ZipClient.cs
+++ b/Emby.Server.Implementations/Archiving/ZipClient.cs
@@ -14,11 +14,9 @@ namespace Emby.Server.Implementations.Archiving
/// </summary>
public class ZipClient : IZipClient
{
- private readonly IFileSystem _fileSystem;
-
- public ZipClient(IFileSystem fileSystem)
+ public ZipClient()
{
- _fileSystem = fileSystem;
+
}
/// <summary>
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 949b89226..7e50650d7 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -243,8 +243,7 @@ namespace Emby.Server.Implementations.Channels
{
foreach (var item in returnItems)
{
- var task = RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None);
- Task.WaitAll(task);
+ RefreshLatestChannelItems(GetChannelProvider(item), CancellationToken.None).GetAwaiter().GetResult();
}
}
@@ -303,9 +302,7 @@ namespace Emby.Server.Implementations.Channels
}
numComplete++;
- double percent = numComplete;
- percent /= allChannelsList.Count;
-
+ double percent = (double)numComplete / allChannelsList.Count;
progress.Report(100 * percent);
}
@@ -658,9 +655,7 @@ namespace Emby.Server.Implementations.Channels
foreach (var item in result.Items)
{
- var folder = item as Folder;
-
- if (folder != null)
+ if (item is Folder folder)
{
await GetChannelItemsInternal(new InternalItemsQuery
{
diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
index ad6c537ef..3c7cbb115 100644
--- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -35,64 +35,52 @@ namespace Emby.Server.Implementations.Channels
public static string GetUserDistinctValue(User user)
{
var channels = user.Policy.EnabledChannels
- .OrderBy(i => i)
- .ToList();
+ .OrderBy(i => i);
- return string.Join("|", channels.ToArray());
+ return string.Join("|", channels);
}
private void CleanDatabase(CancellationToken cancellationToken)
{
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
- var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery
+ var uninstalledChannels = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new[] { typeof(Channel).Name }
+ IncludeItemTypes = new[] { typeof(Channel).Name },
+ ExcludeItemIds = installedChannelIds.ToArray()
});
- var invalidIds = databaseIds
- .Except(installedChannelIds)
- .ToList();
-
- foreach (var id in invalidIds)
+ foreach (var channel in uninstalledChannels)
{
cancellationToken.ThrowIfCancellationRequested();
- CleanChannel(id, cancellationToken);
+ CleanChannel((Channel)channel, cancellationToken);
}
}
- private void CleanChannel(Guid id, CancellationToken cancellationToken)
+ private void CleanChannel(Channel channel, CancellationToken cancellationToken)
{
- _logger.LogInformation("Cleaning channel {0} from database", id);
+ _logger.LogInformation("Cleaning channel {0} from database", channel.Id);
// Delete all channel items
- var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
{
- ChannelIds = new[] { id }
+ ChannelIds = new[] { channel.Id }
});
- foreach (var deleteId in allIds)
+ foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
- DeleteItem(deleteId);
- }
-
- // Finally, delete the channel itself
- DeleteItem(id);
- }
+ _libraryManager.DeleteItem(item, new DeleteOptions
+ {
+ DeleteFileLocation = false
- private void DeleteItem(Guid id)
- {
- var item = _libraryManager.GetItemById(id);
-
- if (item == null)
- {
- return;
+ }, false);
}
- _libraryManager.DeleteItem(item, new DeleteOptions
+ // Finally, delete the channel itself
+ _libraryManager.DeleteItem(channel, new DeleteOptions
{
DeleteFileLocation = false
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index 6aeadda2f..0244c4a68 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -10,14 +10,18 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Collections
{
public class CollectionImageProvider : BaseDynamicImageProvider<BoxSet>
{
- public CollectionImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ public CollectionImageProvider(
+ IFileSystem fileSystem,
+ IProviderManager providerManager,
+ IApplicationPaths applicationPaths,
+ IImageProcessor imageProcessor)
+ : base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
}
@@ -32,7 +36,7 @@ namespace Emby.Server.Implementations.Collections
return base.Supports(item);
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (BoxSet)item;
@@ -76,7 +80,7 @@ namespace Emby.Server.Implementations.Collections
.ToList();
}
- protected override string CreateImage(BaseItem item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
}
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 812e48a1f..2b99e0ddf 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -342,14 +342,12 @@ namespace Emby.Server.Implementations.Collections
{
private readonly CollectionManager _collectionManager;
private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
private ILogger _logger;
- public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
+ public CollectionManagerEntryPoint(ICollectionManager collectionManager, IServerConfigurationManager config, ILogger logger)
{
_collectionManager = (CollectionManager)collectionManager;
_config = config;
- _fileSystem = fileSystem;
_logger = logger;
}
diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs
new file mode 100644
index 000000000..9bc60972a
--- /dev/null
+++ b/Emby.Server.Implementations/ConfigurationOptions.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+
+namespace Emby.Server.Implementations
+{
+ public static class ConfigurationOptions
+ {
+ public static readonly Dictionary<string, string> Configuration = new Dictionary<string, string>
+ {
+ {"HttpListenerHost:DefaultRedirectPath", "web/index.html"},
+ {"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"}
+ };
+ }
+}
diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
index 09fdbc856..982bba625 100644
--- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
+++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs
@@ -1,13 +1,49 @@
using System;
+using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
+using System.Linq;
using MediaBrowser.Model.Cryptography;
namespace Emby.Server.Implementations.Cryptography
{
public class CryptographyProvider : ICryptoProvider
{
+ private static readonly HashSet<string> _supportedHashMethods = new HashSet<string>()
+ {
+ "MD5",
+ "System.Security.Cryptography.MD5",
+ "SHA",
+ "SHA1",
+ "System.Security.Cryptography.SHA1",
+ "SHA256",
+ "SHA-256",
+ "System.Security.Cryptography.SHA256",
+ "SHA384",
+ "SHA-384",
+ "System.Security.Cryptography.SHA384",
+ "SHA512",
+ "SHA-512",
+ "System.Security.Cryptography.SHA512"
+ };
+
+ public string DefaultHashMethod => "PBKDF2";
+
+ private RandomNumberGenerator _randomNumberGenerator;
+
+ private const int _defaultIterations = 1000;
+
+ public CryptographyProvider()
+ {
+ //FIXME: When we get DotNet Standard 2.1 we need to revisit how we do the crypto
+ //Currently supported hash methods from https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptoconfig?view=netcore-2.1
+ //there might be a better way to autogenerate this list as dotnet updates, but I couldn't find one
+ //Please note the default method of PBKDF2 is not included, it cannot be used to generate hashes cleanly as it is actually a pbkdf with sha1
+ _randomNumberGenerator = RandomNumberGenerator.Create();
+ }
+
public Guid GetMD5(string str)
{
return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str)));
@@ -36,5 +72,98 @@ namespace Emby.Server.Implementations.Cryptography
return provider.ComputeHash(bytes);
}
}
+
+ public IEnumerable<string> GetSupportedHashMethods()
+ {
+ return _supportedHashMethods;
+ }
+
+ private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations)
+ {
+ //downgrading for now as we need this library to be dotnetstandard compliant
+ //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment
+ if (method == DefaultHashMethod)
+ {
+ using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations))
+ {
+ return r.GetBytes(32);
+ }
+ }
+
+ throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}");
+ }
+
+ public byte[] ComputeHash(string hashMethod, byte[] bytes)
+ {
+ return ComputeHash(hashMethod, bytes, Array.Empty<byte>());
+ }
+
+ public byte[] ComputeHashWithDefaultMethod(byte[] bytes)
+ {
+ return ComputeHash(DefaultHashMethod, bytes);
+ }
+
+ public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt)
+ {
+ if (hashMethod == DefaultHashMethod)
+ {
+ return PBKDF2(hashMethod, bytes, salt, _defaultIterations);
+ }
+ else if (_supportedHashMethods.Contains(hashMethod))
+ {
+ using (var h = HashAlgorithm.Create(hashMethod))
+ {
+ if (salt.Length == 0)
+ {
+ return h.ComputeHash(bytes);
+ }
+ else
+ {
+ byte[] salted = new byte[bytes.Length + salt.Length];
+ Array.Copy(bytes, salted, bytes.Length);
+ Array.Copy(salt, 0, salted, bytes.Length, salt.Length);
+ return h.ComputeHash(salted);
+ }
+ }
+ }
+ else
+ {
+ throw new CryptographicException($"Requested hash method is not supported: {hashMethod}");
+ }
+ }
+
+ public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt)
+ {
+ return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations);
+ }
+
+ public byte[] ComputeHash(PasswordHash hash)
+ {
+ int iterations = _defaultIterations;
+ if (!hash.Parameters.ContainsKey("iterations"))
+ {
+ hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ try
+ {
+ iterations = int.Parse(hash.Parameters["iterations"]);
+ }
+ catch (Exception e)
+ {
+ throw new InvalidDataException($"Couldn't successfully parse iterations value from string: {hash.Parameters["iterations"]}", e);
+ }
+ }
+
+ return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes, iterations);
+ }
+
+ public byte[] GenerateSalt()
+ {
+ byte[] salt = new byte[64];
+ _randomNumberGenerator.GetBytes(salt);
+ return salt;
+ }
}
}
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 0f432c36c..fba81306b 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -224,7 +224,7 @@ namespace Emby.Server.Implementations.Data
});
}
- db.ExecuteAll(string.Join(";", queries.ToArray()));
+ db.ExecuteAll(string.Join(";", queries));
Logger.LogInformation("PRAGMA synchronous=" + db.Query("PRAGMA synchronous").SelectScalarString().First());
}
@@ -232,23 +232,6 @@ namespace Emby.Server.Implementations.Data
protected virtual int? CacheSize => null;
- internal static void CheckOk(int rc)
- {
- string msg = "";
-
- if (raw.SQLITE_OK != rc)
- {
- throw CreateException((ErrorCode)rc, msg);
- }
- }
-
- internal static Exception CreateException(ErrorCode rc, string msg)
- {
- var exp = new Exception(msg);
-
- return exp;
- }
-
private bool _disposed;
protected void CheckDisposed()
{
@@ -375,13 +358,6 @@ namespace Emby.Server.Implementations.Data
}
}
- public class DummyToken : IDisposable
- {
- public void Dispose()
- {
- }
- }
-
public static IDisposable Read(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
@@ -390,6 +366,7 @@ namespace Emby.Server.Implementations.Data
//}
return new WriteLockToken(obj);
}
+
public static IDisposable Write(this ReaderWriterLockSlim obj)
{
//if (BaseSqliteRepository.ThreadSafeMode > 0)
diff --git a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
index dcfe14943..f7743a3c2 100644
--- a/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
+++ b/Emby.Server.Implementations/Data/CleanDatabaseScheduledTask.cs
@@ -1,11 +1,8 @@
using System;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Data
@@ -13,18 +10,12 @@ namespace Emby.Server.Implementations.Data
public class CleanDatabaseScheduledTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
- private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
- private readonly IApplicationPaths _appPaths;
- public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
+ public CleanDatabaseScheduledTask(ILibraryManager libraryManager, ILogger logger)
{
_libraryManager = libraryManager;
- _itemRepo = itemRepo;
_logger = logger;
- _fileSystem = fileSystem;
- _appPaths = appPaths;
}
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index 3014e482d..06f6563a3 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -536,7 +536,7 @@ namespace Emby.Server.Implementations.Data
throw new ArgumentNullException(nameof(item));
}
- SaveItems(new List<BaseItem> { item }, cancellationToken);
+ SaveItems(new [] { item }, cancellationToken);
}
public void SaveImages(BaseItem item)
@@ -576,7 +576,7 @@ namespace Emby.Server.Implementations.Data
/// or
/// cancellationToken
/// </exception>
- public void SaveItems(List<BaseItem> items, CancellationToken cancellationToken)
+ public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
{
if (items == null)
{
@@ -587,7 +587,7 @@ namespace Emby.Server.Implementations.Data
CheckDisposed();
- var tuples = new List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>>();
+ var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>();
foreach (var item in items)
{
var ancestorIds = item.SupportsAncestors ?
@@ -599,7 +599,7 @@ namespace Emby.Server.Implementations.Data
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
var inheritedTags = item.GetInheritedTags();
- tuples.Add(new Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>(item, ancestorIds, topParent, userdataKey, inheritedTags));
+ tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
}
using (WriteLock.Write())
@@ -615,7 +615,7 @@ namespace Emby.Server.Implementations.Data
}
}
- private void SaveItemsInTranscation(IDatabaseConnection db, List<Tuple<BaseItem, List<Guid>, BaseItem, string, List<string>>> tuples)
+ private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
{
var statements = PrepareAllSafe(db, new string[]
{
@@ -966,7 +966,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0)
{
- saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds.ToArray()));
+ saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
}
else
{
@@ -1183,9 +1183,9 @@ namespace Emby.Server.Implementations.Data
/// <exception cref="ArgumentException"></exception>
public BaseItem RetrieveItem(Guid id)
{
- if (id.Equals(Guid.Empty))
+ if (id == Guid.Empty)
{
- throw new ArgumentNullException(nameof(id));
+ throw new ArgumentException(nameof(id), "Guid can't be empty");
}
CheckDisposed();
@@ -2079,14 +2079,14 @@ namespace Emby.Server.Implementations.Data
return false;
}
- var sortingFields = query.OrderBy.Select(i => i.Item1);
+ var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
- return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.IsPlayed, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.IsUnplayed, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.PlayCount, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.DatePlayed, StringComparer.OrdinalIgnoreCase)
- || sortingFields.Contains(ItemSortBy.SeriesDatePlayed, StringComparer.OrdinalIgnoreCase)
+ return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
+ || sortingFields.Contains(ItemSortBy.IsPlayed)
+ || sortingFields.Contains(ItemSortBy.IsUnplayed)
+ || sortingFields.Contains(ItemSortBy.PlayCount)
+ || sortingFields.Contains(ItemSortBy.DatePlayed)
+ || sortingFields.Contains(ItemSortBy.SeriesDatePlayed)
|| query.IsFavoriteOrLiked.HasValue
|| query.IsFavorite.HasValue
|| query.IsResumable.HasValue
@@ -2094,9 +2094,9 @@ namespace Emby.Server.Implementations.Data
|| query.IsLiked.HasValue;
}
- private readonly List<ItemFields> allFields = Enum.GetNames(typeof(ItemFields))
+ private readonly ItemFields[] _allFields = Enum.GetNames(typeof(ItemFields))
.Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
- .ToList();
+ .ToArray();
private string[] GetColumnNamesFromField(ItemFields field)
{
@@ -2151,18 +2151,26 @@ namespace Emby.Server.Implementations.Data
}
}
- private bool HasProgramAttributes(InternalItemsQuery query)
+ private static readonly HashSet<string> _programExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "MusicAlbum",
- "MusicArtist",
- "PhotoAlbum"
- };
+ "Series",
+ "Season",
+ "MusicAlbum",
+ "MusicArtist",
+ "PhotoAlbum"
+ };
+
+ private static readonly HashSet<string> _programTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "Program",
+ "TvChannel",
+ "LiveTvProgram",
+ "LiveTvTvChannel"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasProgramAttributes(InternalItemsQuery query)
+ {
+ if (_programExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2172,29 +2180,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Program",
- "TvChannel",
- "LiveTvProgram",
- "LiveTvTvChannel"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _programTypes.Contains(x));
}
- private bool HasServiceName(InternalItemsQuery query)
+ private static readonly HashSet<string> _serviceTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "MusicAlbum",
- "MusicArtist",
- "PhotoAlbum"
- };
+ "TvChannel",
+ "LiveTvTvChannel"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasServiceName(InternalItemsQuery query)
+ {
+ if (_programExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2204,27 +2201,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "TvChannel",
- "LiveTvTvChannel"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _serviceTypes.Contains(x));
}
- private bool HasStartDate(InternalItemsQuery query)
+ private static readonly HashSet<string> _startDateTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "MusicAlbum",
- "MusicArtist",
- "PhotoAlbum"
- };
+ "Program",
+ "LiveTvProgram"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private bool HasStartDate(InternalItemsQuery query)
+ {
+ if (_programExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2234,13 +2222,7 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Program",
- "LiveTvProgram"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _startDateTypes.Contains(x));
}
private bool HasEpisodeAttributes(InternalItemsQuery query)
@@ -2263,16 +2245,26 @@ namespace Emby.Server.Implementations.Data
return query.IncludeItemTypes.Contains("Trailer", StringComparer.OrdinalIgnoreCase);
}
- private bool HasArtistFields(InternalItemsQuery query)
+
+ private static readonly HashSet<string> _artistExcludeParentTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
- var excludeParentTypes = new string[]
- {
- "Series",
- "Season",
- "PhotoAlbum"
- };
+ "Series",
+ "Season",
+ "PhotoAlbum"
+ };
- if (excludeParentTypes.Contains(query.ParentType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ private static readonly HashSet<string> _artistsTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "Audio",
+ "MusicAlbum",
+ "MusicVideo",
+ "AudioBook",
+ "AudioPodcast"
+ };
+
+ private bool HasArtistFields(InternalItemsQuery query)
+ {
+ if (_artistExcludeParentTypes.Contains(query.ParentType))
{
return false;
}
@@ -2282,18 +2274,17 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Audio",
- "MusicAlbum",
- "MusicVideo",
- "AudioBook",
- "AudioPodcast"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _artistsTypes.Contains(x));
}
+ private static readonly HashSet<string> _seriesTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ {
+ "Book",
+ "AudioBook",
+ "Episode",
+ "Season"
+ };
+
private bool HasSeriesFields(InternalItemsQuery query)
{
if (string.Equals(query.ParentType, "PhotoAlbum", StringComparison.OrdinalIgnoreCase))
@@ -2306,26 +2297,18 @@ namespace Emby.Server.Implementations.Data
return true;
}
- var types = new string[]
- {
- "Book",
- "AudioBook",
- "Episode",
- "Season"
- };
-
- return types.Any(i => query.IncludeItemTypes.Contains(i, StringComparer.OrdinalIgnoreCase));
+ return query.IncludeItemTypes.Any(x => _seriesTypes.Contains(x));
}
- private string[] GetFinalColumnsToSelect(InternalItemsQuery query, string[] startColumns)
+ private List<string> GetFinalColumnsToSelect(InternalItemsQuery query, IEnumerable<string> startColumns)
{
var list = startColumns.ToList();
- foreach (var field in allFields)
+ foreach (var field in _allFields)
{
if (!HasField(query, field))
{
- foreach (var fieldToRemove in GetColumnNamesFromField(field).ToList())
+ foreach (var fieldToRemove in GetColumnNamesFromField(field))
{
list.Remove(fieldToRemove);
}
@@ -2419,11 +2402,14 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString());
- var excludeIds = query.ExcludeItemIds.ToList();
- excludeIds.Add(item.Id);
- excludeIds.AddRange(item.ExtraIds);
+ var oldLen = query.ExcludeItemIds.Length;
+ var newLen = oldLen + item.ExtraIds.Length + 1;
+ var excludeIds = new Guid[newLen];
+ query.ExcludeItemIds.CopyTo(excludeIds, 0);
+ excludeIds[oldLen] = item.Id;
+ item.ExtraIds.CopyTo(excludeIds, oldLen + 1);
- query.ExcludeItemIds = excludeIds.ToArray();
+ query.ExcludeItemIds = excludeIds;
query.ExcludeProviderIds = item.ProviderIds;
}
@@ -2444,7 +2430,7 @@ namespace Emby.Server.Implementations.Data
list.Add(builder.ToString());
}
- return list.ToArray();
+ return list;
}
private void BindSearchParams(InternalItemsQuery query, IStatement statement)
@@ -2723,18 +2709,17 @@ namespace Emby.Server.Implementations.Data
private void AddItem(List<BaseItem> items, BaseItem newItem)
{
- var providerIds = newItem.ProviderIds.ToList();
-
for (var i = 0; i < items.Count; i++)
{
var item = items[i];
- foreach (var providerId in providerIds)
+ foreach (var providerId in newItem.ProviderIds)
{
if (providerId.Key == MetadataProviders.TmdbCollection.ToString())
{
continue;
}
+
if (item.GetProviderId(providerId.Key) == providerId.Value)
{
if (newItem.SourceType == SourceType.Library)
@@ -2753,15 +2738,15 @@ namespace Emby.Server.Implementations.Data
{
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
- int slowThreshold = 1000;
+ int slowThreshold = 100;
#if DEBUG
- slowThreshold = 250;
+ slowThreshold = 10;
#endif
if (elapsed >= slowThreshold)
{
- Logger.LogWarning("{0} query time (slow): {1}ms. Query: {2}",
+ Logger.LogWarning("{0} query time (slow): {1:g}. Query: {2}",
methodName,
elapsed,
commandText);
@@ -2806,7 +2791,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses);
commandText += whereText
+ GetGroupBy(query)
@@ -2930,25 +2915,31 @@ namespace Emby.Server.Implementations.Data
private string GetOrderByText(InternalItemsQuery query)
{
- var orderBy = query.OrderBy.ToList();
- var enableOrderInversion = false;
-
- if (query.SimilarTo != null && orderBy.Count == 0)
+ if (string.IsNullOrEmpty(query.SearchTerm))
{
- orderBy.Add(new ValueTuple<string, SortOrder>("SimilarityScore", SortOrder.Descending));
- orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending));
- }
+ int oldLen = query.OrderBy.Length;
- if (!string.IsNullOrEmpty(query.SearchTerm))
+ if (query.SimilarTo != null && oldLen == 0)
+ {
+ var arr = new (string, SortOrder)[oldLen + 2];
+ query.OrderBy.CopyTo(arr, 0);
+ arr[oldLen] = ("SimilarityScore", SortOrder.Descending);
+ arr[oldLen + 1] = (ItemSortBy.Random, SortOrder.Ascending);
+ query.OrderBy = arr;
+ }
+ }
+ else
{
- orderBy = new List<(string, SortOrder)>();
- orderBy.Add(new ValueTuple<string, SortOrder>("SearchScore", SortOrder.Descending));
- orderBy.Add(new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending));
+ query.OrderBy = new []
+ {
+ ("SearchScore", SortOrder.Descending),
+ (ItemSortBy.SortName, SortOrder.Ascending)
+ };
}
- query.OrderBy = orderBy.ToArray();
+ var orderBy = query.OrderBy;
- if (orderBy.Count == 0)
+ if (orderBy.Length == 0)
{
return string.Empty;
}
@@ -2957,6 +2948,7 @@ namespace Emby.Server.Implementations.Data
{
var columnMap = MapOrderByField(i.Item1, query);
var columnAscending = i.Item2 == SortOrder.Ascending;
+ const bool enableOrderInversion = false;
if (columnMap.Item2 && enableOrderInversion)
{
columnAscending = !columnAscending;
@@ -2968,7 +2960,7 @@ namespace Emby.Server.Implementations.Data
}));
}
- private ValueTuple<string, bool> MapOrderByField(string name, InternalItemsQuery query)
+ private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
@@ -3218,7 +3210,7 @@ namespace Emby.Server.Implementations.Data
var whereText = whereClauses.Count == 0 ?
string.Empty :
- " where " + string.Join(" AND ", whereClauses.ToArray());
+ " where " + string.Join(" AND ", whereClauses);
commandText += whereText
+ GetGroupBy(query)
@@ -4378,7 +4370,7 @@ namespace Emby.Server.Implementations.Data
}
else if (query.Years.Length > 1)
{
- var val = string.Join(",", query.Years.ToArray());
+ var val = string.Join(",", query.Years);
whereClauses.Add("ProductionYear in (" + val + ")");
}
@@ -4952,7 +4944,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return result;
}
- return new[] { value }.Where(IsValidType);
+ if (IsValidType(value))
+ {
+ return new[] { value };
+ }
+
+ return Array.Empty<string>();
}
public void DeleteItem(Guid id, CancellationToken cancellationToken)
@@ -5215,32 +5212,32 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
}
@@ -5317,7 +5314,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
}
}
- private QueryResult<Tuple<BaseItem, ItemCounts>> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
+ private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
{
if (query == null)
{
@@ -5335,7 +5332,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
- ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray()) + ")");
+ ("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
InternalItemsQuery typeSubQuery = null;
@@ -5363,11 +5360,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
whereClauses.Add("guid in (select ItemId from ItemValues where ItemValues.CleanValue=A.CleanName AND " + typeClause + ")");
- var typeWhereText = whereClauses.Count == 0 ?
- string.Empty :
- " where " + string.Join(" AND ", whereClauses);
-
- itemCountColumnQuery += typeWhereText;
+ itemCountColumnQuery += " where " + string.Join(" AND ", whereClauses);
itemCountColumns = new Dictionary<string, string>()
{
@@ -5400,7 +5393,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
IsSeries = query.IsSeries
};
- columns = GetFinalColumnsToSelect(query, columns.ToArray()).ToList();
+ columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select "
+ string.Join(",", columns)
@@ -5492,8 +5485,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
return connection.RunInTransaction(db =>
{
- var list = new List<Tuple<BaseItem, ItemCounts>>();
- var result = new QueryResult<Tuple<BaseItem, ItemCounts>>();
+ var list = new List<(BaseItem, ItemCounts)>();
+ var result = new QueryResult<(BaseItem, ItemCounts)>();
var statements = PrepareAllSafe(db, statementTexts);
@@ -5531,7 +5524,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
{
var countStartColumn = columns.Count - 1;
- list.Add(new Tuple<BaseItem, ItemCounts>(item, GetItemCounts(row, countStartColumn, typesToCount)));
+ list.Add((item, GetItemCounts(row, countStartColumn, typesToCount)));
}
}
@@ -6198,6 +6191,5 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
return item;
}
-
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 7a9b72244..4109b7ad1 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -119,9 +119,9 @@ namespace Emby.Server.Implementations.Data
{
list.Add(row[0].ReadGuidFromBlob());
}
- catch
+ catch (Exception ex)
{
-
+ Logger.LogError(ex, "Error while getting user");
}
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs
index db359d7dd..182df0edc 100644
--- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs
@@ -55,6 +55,8 @@ namespace Emby.Server.Implementations.Data
{
TryMigrateToLocalUsersTable(connection);
}
+
+ RemoveEmptyPasswordHashes();
}
}
@@ -73,6 +75,38 @@ namespace Emby.Server.Implementations.Data
}
}
+ private void RemoveEmptyPasswordHashes()
+ {
+ foreach (var user in RetrieveAllUsers())
+ {
+ // If the user password is the sha1 hash of the empty string, remove it
+ if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)
+ || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ user.Password = null;
+ var serialized = _jsonSerializer.SerializeToBytes(user);
+
+ using (WriteLock.Write())
+ using (var connection = CreateConnection())
+ {
+ connection.RunInTransaction(db =>
+ {
+ using (var statement = db.PrepareStatement("update LocalUsersv2 set data=@data where Id=@InternalId"))
+ {
+ statement.TryBind("@InternalId", user.InternalId);
+ statement.TryBind("@data", serialized);
+ statement.MoveNext();
+ }
+
+ }, TransactionMode);
+ }
+ }
+
+ }
+
/// <summary>
/// Save a user in the repo
/// </summary>
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index 866bd137f..495c3436a 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -11,7 +11,6 @@ namespace Emby.Server.Implementations.Devices
{
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private readonly object _syncLock = new object();
@@ -86,19 +85,10 @@ namespace Emby.Server.Implementations.Devices
private string _id;
- public DeviceId(
- IApplicationPaths appPaths,
- ILoggerFactory loggerFactory,
- IFileSystem fileSystem)
+ public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
{
- if (fileSystem == null)
- {
- throw new ArgumentNullException(nameof(fileSystem));
- }
-
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger("SystemId");
- _fileSystem = fileSystem;
}
public string Value => _id ?? (_id = GetDeviceId());
diff --git a/Emby.Server.Implementations/Devices/DeviceManager.cs b/Emby.Server.Implementations/Devices/DeviceManager.cs
index ec3649bca..7d6529a67 100644
--- a/Emby.Server.Implementations/Devices/DeviceManager.cs
+++ b/Emby.Server.Implementations/Devices/DeviceManager.cs
@@ -34,8 +34,6 @@ namespace Emby.Server.Implementations.Devices
private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IServerConfigurationManager _config;
- private readonly ILogger _logger;
- private readonly INetworkManager _network;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
@@ -55,17 +53,13 @@ namespace Emby.Server.Implementations.Devices
IUserManager userManager,
IFileSystem fileSystem,
ILibraryMonitor libraryMonitor,
- IServerConfigurationManager config,
- ILoggerFactory loggerFactory,
- INetworkManager network)
+ IServerConfigurationManager config)
{
_json = json;
_userManager = userManager;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_config = config;
- _logger = loggerFactory.CreateLogger(nameof(DeviceManager));
- _network = network;
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_authRepo = authRepo;
@@ -414,14 +408,12 @@ namespace Emby.Server.Implementations.Devices
{
private readonly DeviceManager _deviceManager;
private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
private ILogger _logger;
- public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
+ public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, ILogger logger)
{
_deviceManager = (DeviceManager)deviceManager;
_config = config;
- _fileSystem = fileSystem;
_logger = logger;
}
diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
index 78b22bda3..2c4ef170d 100644
--- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
+++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs
@@ -130,7 +130,7 @@ namespace Emby.Server.Implementations.Diagnostics
public void Dispose()
{
- _process.Dispose();
+ _process?.Dispose();
}
}
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index 983eb51e6..7b28a22a8 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -5,8 +5,6 @@ using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Common;
using MediaBrowser.Controller.Channels;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
@@ -21,8 +19,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
@@ -36,13 +32,9 @@ namespace Emby.Server.Implementations.Dto
private readonly IItemRepository _itemRepo;
private readonly IImageProcessor _imageProcessor;
- private readonly IServerConfigurationManager _config;
- private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager;
- private readonly Func<IChannelManager> _channelManagerFactory;
private readonly IApplicationHost _appHost;
- private readonly Func<IDeviceManager> _deviceManager;
private readonly Func<IMediaSourceManager> _mediaSourceManager;
private readonly Func<ILiveTvManager> _livetvManager;
@@ -52,12 +44,8 @@ namespace Emby.Server.Implementations.Dto
IUserDataManager userDataRepository,
IItemRepository itemRepo,
IImageProcessor imageProcessor,
- IServerConfigurationManager config,
- IFileSystem fileSystem,
IProviderManager providerManager,
- Func<IChannelManager> channelManagerFactory,
IApplicationHost appHost,
- Func<IDeviceManager> deviceManager,
Func<IMediaSourceManager> mediaSourceManager,
Func<ILiveTvManager> livetvManager)
{
@@ -66,12 +54,8 @@ namespace Emby.Server.Implementations.Dto
_userDataRepository = userDataRepository;
_itemRepo = itemRepo;
_imageProcessor = imageProcessor;
- _config = config;
- _fileSystem = fileSystem;
_providerManager = providerManager;
- _channelManagerFactory = channelManagerFactory;
_appHost = appHost;
- _deviceManager = deviceManager;
_mediaSourceManager = mediaSourceManager;
_livetvManager = livetvManager;
}
@@ -95,15 +79,8 @@ namespace Emby.Server.Implementations.Dto
return GetBaseItemDto(item, options, user, owner);
}
- public BaseItemDto[] GetBaseItemDtos(List<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
- {
- return GetBaseItemDtos(items, items.Count, options, user, owner);
- }
-
- public BaseItemDto[] GetBaseItemDtos(BaseItem[] items, DtoOptions options, User user = null, BaseItem owner = null)
- {
- return GetBaseItemDtos(items, items.Length, options, user, owner);
- }
+ public BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
+ => GetBaseItemDtos(items, items.Count, options, user, owner);
public BaseItemDto[] GetBaseItemDtos(IEnumerable<BaseItem> items, int itemCount, DtoOptions options, User user = null, BaseItem owner = null)
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 6bf776f53..39e9ed375 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -9,7 +9,6 @@
<ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj" />
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
- <ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj" />
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
@@ -22,8 +21,17 @@
</ItemGroup>
<ItemGroup>
+ <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
+ <PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
+ <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
<PackageReference Include="sharpcompress" Version="0.22.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs
deleted file mode 100644
index 60cd7b3d7..000000000
--- a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-namespace Emby.Server.Implementations.FFMpeg
-{
- /// <summary>
- /// Class FFMpegInfo
- /// </summary>
- public class FFMpegInfo
- {
- /// <summary>
- /// Gets or sets the path.
- /// </summary>
- /// <value>The path.</value>
- public string EncoderPath { get; set; }
- /// <summary>
- /// Gets or sets the probe path.
- /// </summary>
- /// <value>The probe path.</value>
- public string ProbePath { get; set; }
- /// <summary>
- /// Gets or sets the version.
- /// </summary>
- /// <value>The version.</value>
- public string Version { get; set; }
- }
-}
diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs
deleted file mode 100644
index fa9cb5e01..000000000
--- a/Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Emby.Server.Implementations.FFMpeg
-{
- public class FFMpegInstallInfo
- {
- public string Version { get; set; }
- public string FFMpegFilename { get; set; }
- public string FFProbeFilename { get; set; }
- public string ArchiveType { get; set; }
-
- public FFMpegInstallInfo()
- {
- Version = "Path";
- FFMpegFilename = "ffmpeg";
- FFProbeFilename = "ffprobe";
- }
- }
-}
diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs b/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
deleted file mode 100644
index 6167d1eaa..000000000
--- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs
+++ /dev/null
@@ -1,141 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.FFMpeg
-{
- public class FFMpegLoader
- {
- private readonly IHttpClient _httpClient;
- private readonly IApplicationPaths _appPaths;
- private readonly ILogger _logger;
- private readonly IZipClient _zipClient;
- private readonly IFileSystem _fileSystem;
- private readonly FFMpegInstallInfo _ffmpegInstallInfo;
-
- public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
- {
- _logger = logger;
- _appPaths = appPaths;
- _httpClient = httpClient;
- _zipClient = zipClient;
- _fileSystem = fileSystem;
- _ffmpegInstallInfo = ffmpegInstallInfo;
- }
-
- public FFMpegInfo GetFFMpegInfo(IStartupOptions options)
- {
- var customffMpegPath = options.FFmpegPath;
- var customffProbePath = options.FFprobePath;
-
- if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
- {
- return new FFMpegInfo
- {
- ProbePath = customffProbePath,
- EncoderPath = customffMpegPath,
- Version = "external"
- };
- }
-
- var downloadInfo = _ffmpegInstallInfo;
-
- var prebuiltFolder = _appPaths.ProgramSystemPath;
- var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename);
- var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename);
- if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe))
- {
- return new FFMpegInfo
- {
- ProbePath = prebuiltffprobe,
- EncoderPath = prebuiltffmpeg,
- Version = "external"
- };
- }
-
- var version = downloadInfo.Version;
-
- if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
- {
- return new FFMpegInfo();
- }
-
- var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
- var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
-
- var info = new FFMpegInfo
- {
- ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
- EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
- Version = version
- };
-
- Directory.CreateDirectory(versionedDirectoryPath);
-
- var excludeFromDeletions = new List<string> { versionedDirectoryPath };
-
- if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath))
- {
- // ffmpeg not present. See if there's an older version we can start with
- var existingVersion = GetExistingVersion(info, rootEncoderPath);
-
- // No older version. Need to download and block until complete
- if (existingVersion == null)
- {
- return new FFMpegInfo();
- }
- else
- {
- info = existingVersion;
- versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
- excludeFromDeletions.Add(versionedDirectoryPath);
- }
- }
-
- // Allow just one of these to be overridden, if desired.
- if (!string.IsNullOrWhiteSpace(customffMpegPath))
- {
- info.EncoderPath = customffMpegPath;
- }
- if (!string.IsNullOrWhiteSpace(customffProbePath))
- {
- info.ProbePath = customffProbePath;
- }
-
- return info;
- }
-
- private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
- {
- var encoderFilename = Path.GetFileName(info.EncoderPath);
- var probeFilename = Path.GetFileName(info.ProbePath);
-
- foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath)
- .ToList())
- {
- var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();
-
- var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
- var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
-
- if (!string.IsNullOrWhiteSpace(encoder) &&
- !string.IsNullOrWhiteSpace(probe))
- {
- return new FFMpegInfo
- {
- EncoderPath = encoder,
- ProbePath = probe,
- Version = Path.GetFileName(Path.GetDirectoryName(probe))
- };
- }
- }
-
- return null;
- }
- }
-}
diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
index 2232b3eeb..1bebdd163 100644
--- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpClientManager
{
@@ -179,11 +180,11 @@ namespace Emby.Server.Implementations.HttpClientManager
foreach (var header in options.RequestHeaders)
{
- if (string.Equals(header.Key, "Accept", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(header.Key, HeaderNames.Accept, StringComparison.OrdinalIgnoreCase))
{
request.Accept = header.Value;
}
- else if (string.Equals(header.Key, "User-Agent", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(header.Key, HeaderNames.UserAgent, StringComparison.OrdinalIgnoreCase))
{
SetUserAgent(request, header.Value);
hasUserAgent = true;
@@ -327,7 +328,6 @@ namespace Emby.Server.Implementations.HttpClientManager
}
httpWebRequest.ContentType = contentType;
- httpWebRequest.ContentLength = bytes.Length;
(await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length);
}
catch (Exception ex)
@@ -539,21 +539,10 @@ namespace Emby.Server.Implementations.HttpClientManager
var contentLength = GetContentLength(httpResponse);
- if (contentLength.HasValue)
- {
- using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await httpResponse.GetResponseStream().CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
- }
- }
- else
+ using (var stream = httpResponse.GetResponseStream())
+ using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
{
- // We're not able to track progress
- using (var stream = httpResponse.GetResponseStream())
- using (var fs = _fileSystem.GetFileStream(tempFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
- {
- await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
- }
+ await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
}
options.Progress.Report(100);
diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs
index 7aedba9b3..c4d2a70e2 100644
--- a/Emby.Server.Implementations/HttpServer/FileWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs
@@ -5,15 +5,19 @@ using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
public class FileWriter : IHttpResult
{
+ private readonly IStreamHelper _streamHelper;
private ILogger Logger { get; set; }
+ private readonly IFileSystem _fileSystem;
private string RangeHeader { get; set; }
private bool IsHeadRequest { get; set; }
@@ -42,25 +46,27 @@ namespace Emby.Server.Implementations.HttpServer
public string Path { get; set; }
- public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem)
+ public FileWriter(string path, string contentType, string rangeHeader, ILogger logger, IFileSystem fileSystem, IStreamHelper streamHelper)
{
if (string.IsNullOrEmpty(contentType))
{
throw new ArgumentNullException(nameof(contentType));
}
+ _streamHelper = streamHelper;
+ _fileSystem = fileSystem;
+
Path = path;
Logger = logger;
RangeHeader = rangeHeader;
- Headers["Content-Type"] = contentType;
+ Headers[HeaderNames.ContentType] = contentType;
TotalContentLength = fileSystem.GetFileInfo(path).Length;
- Headers["Accept-Ranges"] = "bytes";
+ Headers[HeaderNames.AcceptRanges] = "bytes";
if (string.IsNullOrWhiteSpace(rangeHeader))
{
- Headers["Content-Length"] = TotalContentLength.ToString(UsCulture);
StatusCode = HttpStatusCode.OK;
}
else
@@ -93,13 +99,10 @@ namespace Emby.Server.Implementations.HttpServer
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
- // Content-Length is the length of what we're serving, not the original content
- var lengthString = RangeLength.ToString(UsCulture);
- Headers["Content-Length"] = lengthString;
- var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
- Headers["Content-Range"] = rangeString;
+ var rangeString = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
+ Headers[HeaderNames.ContentRange] = rangeString;
- Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Length: {2}, Content-Range: {3}", Path, RangeHeader, lengthString, rangeString);
+ Logger.LogInformation("Setting range response values for {0}. RangeRequest: {1} Content-Range: {2}", Path, RangeHeader, rangeString);
}
/// <summary>
@@ -145,8 +148,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- private string[] SkipLogExtensions = new string[]
- {
+ private static readonly string[] SkipLogExtensions = {
".js",
".html",
".css"
@@ -163,8 +165,10 @@ namespace Emby.Server.Implementations.HttpServer
}
var path = Path;
+ var offset = RangeStart;
+ var count = RangeLength;
- if (string.IsNullOrWhiteSpace(RangeHeader) || (RangeStart <= 0 && RangeEnd >= TotalContentLength - 1))
+ if (string.IsNullOrWhiteSpace(RangeHeader) || RangeStart <= 0 && RangeEnd >= TotalContentLength - 1)
{
var extension = System.IO.Path.GetExtension(path);
@@ -173,20 +177,15 @@ namespace Emby.Server.Implementations.HttpServer
Logger.LogDebug("Transmit file {0}", path);
}
- //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0;
-
- await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false);
- return;
+ offset = 0;
+ count = 0;
}
- await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false);
+ await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false);
}
finally
{
- if (OnComplete != null)
- {
- OnComplete();
- }
+ OnComplete?.Invoke();
}
}
@@ -203,8 +202,5 @@ namespace Emby.Server.Implementations.HttpServer
get => (HttpStatusCode)Status;
set => Status = (int)value;
}
-
- public string StatusDescription { get; set; }
-
}
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
index 834ffb130..e8d47cad5 100644
--- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
@@ -10,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
using Emby.Server.Implementations.Services;
+using Emby.Server.Implementations.SocketSharp;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@@ -19,19 +21,20 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using ServiceStack.Text.Jsv;
namespace Emby.Server.Implementations.HttpServer
{
public class HttpListenerHost : IHttpServer, IDisposable
{
private string DefaultRedirectPath { get; set; }
-
- private readonly ILogger _logger;
public string[] UrlPrefixes { get; private set; }
- private IHttpListener _listener;
-
public event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
private readonly IServerConfigurationManager _config;
@@ -39,6 +42,7 @@ namespace Emby.Server.Implementations.HttpServer
private readonly IServerApplicationHost _appHost;
private readonly IJsonSerializer _jsonSerializer;
private readonly IXmlSerializer _xmlSerializer;
+ private readonly IHttpListener _socketListener;
private readonly Func<Type, Func<string, object>> _funcParseFn;
public Action<IRequest, IResponse, object>[] ResponseFilters { get; set; }
@@ -53,20 +57,23 @@ namespace Emby.Server.Implementations.HttpServer
IServerApplicationHost applicationHost,
ILoggerFactory loggerFactory,
IServerConfigurationManager config,
- string defaultRedirectPath,
+ IConfiguration configuration,
INetworkManager networkManager,
IJsonSerializer jsonSerializer,
IXmlSerializer xmlSerializer,
- Func<Type, Func<string, object>> funcParseFn)
+ IHttpListener socketListener)
{
_appHost = applicationHost;
- _logger = loggerFactory.CreateLogger("HttpServer");
+ Logger = loggerFactory.CreateLogger("HttpServer");
_config = config;
- DefaultRedirectPath = defaultRedirectPath;
+ DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"];
_networkManager = networkManager;
_jsonSerializer = jsonSerializer;
_xmlSerializer = xmlSerializer;
- _funcParseFn = funcParseFn;
+ _socketListener = socketListener;
+ _socketListener.WebSocketConnected = OnWebSocketConnected;
+
+ _funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
Instance = this;
ResponseFilters = Array.Empty<Action<IRequest, IResponse, object>>();
@@ -74,7 +81,7 @@ namespace Emby.Server.Implementations.HttpServer
public string GlobalResponse { get; set; }
- protected ILogger Logger => _logger;
+ protected ILogger Logger { get; }
public object CreateInstance(Type type)
{
@@ -140,11 +147,11 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, _logger)
+ var connection = new WebSocketConnection(e.WebSocket, e.Endpoint, _jsonSerializer, Logger)
{
OnReceive = ProcessWebSocketMessageReceived,
Url = e.Url,
- QueryString = e.QueryString ?? new QueryParamCollection()
+ QueryString = e.QueryString ?? new QueryCollection()
};
connection.Closed += Connection_Closed;
@@ -209,16 +216,16 @@ namespace Emby.Server.Implementations.HttpServer
if (logExceptionStackTrace)
{
- _logger.LogError(ex, "Error processing request");
+ Logger.LogError(ex, "Error processing request");
}
else if (logExceptionMessage)
{
- _logger.LogError(ex.Message);
+ Logger.LogError(ex.Message);
}
var httpRes = httpReq.Response;
- if (httpRes.IsClosed)
+ if (httpRes.OriginalResponse.HasStarted)
{
return;
}
@@ -231,7 +238,7 @@ namespace Emby.Server.Implementations.HttpServer
}
catch (Exception errorEx)
{
- _logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
+ Logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
}
}
@@ -274,39 +281,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
-
- if (_listener != null)
- {
- _logger.LogInformation("Stopping HttpListener...");
- var task = _listener.Stop();
- Task.WaitAll(task);
- _logger.LogInformation("HttpListener stopped");
- }
- }
-
- private static readonly string[] _skipLogExtensions =
- {
- ".js",
- ".css",
- ".woff",
- ".woff2",
- ".ttf",
- ".html"
- };
-
- private bool EnableLogging(string url, string localPath)
- {
- var extension = GetExtension(url);
-
- return ((string.IsNullOrEmpty(extension) || !_skipLogExtensions.Contains(extension))
- && (string.IsNullOrEmpty(localPath) || localPath.IndexOf("system/ping", StringComparison.OrdinalIgnoreCase) == -1));
- }
-
- private static string GetExtension(string url)
- {
- var parts = url.Split(new[] { '?' }, 2);
-
- return Path.GetExtension(parts[0]);
}
public static string RemoveQueryStringByKey(string url, string key)
@@ -314,7 +288,7 @@ namespace Emby.Server.Implementations.HttpServer
var uri = new Uri(url);
// this gets all the query string key value pairs as a collection
- var newQueryString = MyHttpUtility.ParseQueryString(uri.Query);
+ var newQueryString = QueryHelpers.ParseQuery(uri.Query);
var originalCount = newQueryString.Count;
@@ -335,7 +309,7 @@ namespace Emby.Server.Implementations.HttpServer
string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0];
return newQueryString.Count > 0
- ? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString)
+ ? QueryHelpers.AddQueryString(pagePathWithoutQueryString, newQueryString.ToDictionary(kv => kv.Key, kv => kv.Value.ToString()))
: pagePathWithoutQueryString;
}
@@ -444,12 +418,11 @@ namespace Emby.Server.Implementations.HttpServer
/// <summary>
/// Overridable method that can be used to implement a custom hnandler
/// </summary>
- protected async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
+ public async Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, CancellationToken cancellationToken)
{
- var date = DateTime.Now;
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
var httpRes = httpReq.Response;
- bool enableLog = false;
- bool logHeaders = false;
string urlToLog = null;
string remoteIp = httpReq.RemoteIp;
@@ -496,18 +469,8 @@ namespace Emby.Server.Implementations.HttpServer
return;
}
- var operationName = httpReq.OperationName;
-
- enableLog = EnableLogging(urlString, localPath);
- urlToLog = urlString;
- logHeaders = enableLog && urlToLog.IndexOf("/videos/", StringComparison.OrdinalIgnoreCase) != -1;
-
- if (enableLog)
- {
- urlToLog = GetUrlToLog(urlString);
-
- LoggerUtils.LogRequest(_logger, urlToLog, httpReq.HttpMethod, httpReq.UserAgent, logHeaders ? httpReq.Headers : null);
- }
+ urlToLog = GetUrlToLog(urlString);
+ Logger.LogDebug("HTTP {HttpMethod} {Url} UserAgent: {UserAgent} \nHeaders: {@Headers}", urlToLog, httpReq.UserAgent ?? string.Empty, httpReq.HttpMethod, httpReq.Headers);
if (string.Equals(localPath, "/emby/", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser/", StringComparison.OrdinalIgnoreCase))
@@ -515,6 +478,7 @@ namespace Emby.Server.Implementations.HttpServer
RedirectToUrl(httpRes, DefaultRedirectPath);
return;
}
+
if (string.Equals(localPath, "/emby", StringComparison.OrdinalIgnoreCase) ||
string.Equals(localPath, "/mediabrowser", StringComparison.OrdinalIgnoreCase))
{
@@ -560,16 +524,19 @@ namespace Emby.Server.Implementations.HttpServer
RedirectToUrl(httpRes, DefaultRedirectPath);
return;
}
+
if (string.Equals(localPath, "/web/", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, "../" + DefaultRedirectPath);
return;
}
+
if (string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase))
{
RedirectToUrl(httpRes, DefaultRedirectPath);
return;
}
+
if (string.IsNullOrEmpty(localPath))
{
RedirectToUrl(httpRes, "/" + DefaultRedirectPath);
@@ -605,33 +572,21 @@ namespace Emby.Server.Implementations.HttpServer
if (handler != null)
{
- await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false);
+ await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, httpReq.OperationName, cancellationToken).ConfigureAwait(false);
}
else
{
await ErrorHandler(new FileNotFoundException(), httpReq, false, false).ConfigureAwait(false);
}
}
- catch (OperationCanceledException ex)
+ catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
{
await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
}
-
- catch (IOException ex)
- {
- await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
- }
-
- catch (SocketException ex)
- {
- await ErrorHandler(ex, httpReq, false, false).ConfigureAwait(false);
- }
-
catch (SecurityException ex)
{
await ErrorHandler(ex, httpReq, false, true).ConfigureAwait(false);
}
-
catch (Exception ex)
{
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
@@ -640,15 +595,15 @@ namespace Emby.Server.Implementations.HttpServer
}
finally
{
- httpRes.Close();
-
- if (enableLog)
+ stopWatch.Stop();
+ var elapsed = stopWatch.Elapsed;
+ if (elapsed.TotalMilliseconds > 500)
{
- var statusCode = httpRes.StatusCode;
-
- var duration = DateTime.Now - date;
-
- LoggerUtils.LogResponse(_logger, statusCode, urlToLog, remoteIp, duration, logHeaders ? httpRes.Headers : null);
+ Logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
+ }
+ else
+ {
+ Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog);
}
}
}
@@ -661,12 +616,11 @@ namespace Emby.Server.Implementations.HttpServer
var pathParts = pathInfo.TrimStart('/').Split('/');
if (pathParts.Length == 0)
{
- _logger.LogError("Path parts empty for PathInfo: {pathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
+ Logger.LogError("Path parts empty for PathInfo: {PathInfo}, Url: {RawUrl}", pathInfo, httpReq.RawUrl);
return null;
}
var restPath = ServiceHandler.FindMatchingRestPath(httpReq.HttpMethod, pathInfo, out string contentType);
-
if (restPath != null)
{
return new ServiceHandler
@@ -676,15 +630,13 @@ namespace Emby.Server.Implementations.HttpServer
};
}
- _logger.LogError("Could not find handler for {PathInfo}", pathInfo);
+ Logger.LogError("Could not find handler for {PathInfo}", pathInfo);
return null;
}
private static Task Write(IResponse response, string text)
{
var bOutput = Encoding.UTF8.GetBytes(text);
- response.SetContentLength(bOutput.Length);
-
return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length);
}
@@ -703,6 +655,7 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
+ // TODO what is this?
var httpsUrl = url
.Replace("http://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace(":" + _config.Configuration.PublicPort.ToString(CultureInfo.InvariantCulture), ":" + _config.Configuration.PublicHttpsPort.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase);
@@ -723,13 +676,15 @@ namespace Emby.Server.Implementations.HttpServer
/// Adds the rest handlers.
/// </summary>
/// <param name="services">The services.</param>
- public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners)
+ /// <param name="listeners"></param>
+ /// <param name="urlPrefixes"></param>
+ public void Init(IEnumerable<IService> services, IEnumerable<IWebSocketListener> listeners, IEnumerable<string> urlPrefixes)
{
_webSocketListeners = listeners.ToArray();
-
+ UrlPrefixes = urlPrefixes.ToArray();
ServiceController = new ServiceController();
- _logger.LogInformation("Calling ServiceStack AppHost.Init");
+ Logger.LogInformation("Calling ServiceStack AppHost.Init");
var types = services.Select(r => r.GetType()).ToArray();
@@ -737,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer
ResponseFilters = new Action<IRequest, IResponse, object>[]
{
- new ResponseFilter(_logger).FilterResponse
+ new ResponseFilter(Logger).FilterResponse
};
}
@@ -799,8 +754,12 @@ namespace Emby.Server.Implementations.HttpServer
return _jsonSerializer.DeserializeFromStreamAsync(stream, type);
}
- //TODO Add Jellyfin Route Path Normalizer
+ public Task ProcessWebSocketRequest(HttpContext context)
+ {
+ return _socketListener.ProcessWebSocketRequest(context);
+ }
+ //TODO Add Jellyfin Route Path Normalizer
private static string NormalizeEmbyRoutePath(string path)
{
if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
@@ -833,6 +792,7 @@ namespace Emby.Server.Implementations.HttpServer
private bool _disposed;
private readonly object _disposeLock = new object();
+
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
@@ -861,7 +821,7 @@ namespace Emby.Server.Implementations.HttpServer
return Task.CompletedTask;
}
- _logger.LogDebug("Websocket message received: {0}", result.MessageType);
+ Logger.LogDebug("Websocket message received: {0}", result.MessageType);
var tasks = _webSocketListeners.Select(i => Task.Run(async () =>
{
@@ -871,7 +831,7 @@ namespace Emby.Server.Implementations.HttpServer
}
catch (Exception ex)
{
- _logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
+ Logger.LogError(ex, "{0} failed processing WebSocket message {1}", i.GetType().Name, result.MessageType ?? string.Empty);
}
}));
@@ -882,18 +842,5 @@ namespace Emby.Server.Implementations.HttpServer
{
Dispose(true);
}
-
- public void StartServer(string[] urlPrefixes, IHttpListener httpListener)
- {
- UrlPrefixes = urlPrefixes;
-
- _listener = httpListener;
-
- _listener.WebSocketConnected = OnWebSocketConnected;
- _listener.ErrorHandler = ErrorHandler;
- _listener.RequestHandler = RequestHandler;
-
- _listener.Start(UrlPrefixes);
- }
}
}
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index 75ca57ebb..463265862 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -16,6 +16,8 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
using IRequest = MediaBrowser.Model.Services.IRequest;
using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
@@ -32,17 +34,16 @@ namespace Emby.Server.Implementations.HttpServer
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IJsonSerializer _jsonSerializer;
-
- private IBrotliCompressor _brotliCompressor;
+ private readonly IStreamHelper _streamHelper;
/// <summary>
/// Initializes a new instance of the <see cref="HttpResultFactory" /> class.
/// </summary>
- public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor)
+ public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IStreamHelper streamHelper)
{
_fileSystem = fileSystem;
_jsonSerializer = jsonSerializer;
- _brotliCompressor = brotliCompressor;
+ _streamHelper = streamHelper;
_logger = loggerfactory.CreateLogger("HttpResultFactory");
}
@@ -76,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer
public object GetRedirectResult(string url)
{
var responseHeaders = new Dictionary<string, string>();
- responseHeaders["Location"] = url;
+ responseHeaders[HeaderNames.Location] = url;
var result = new HttpResult(Array.Empty<byte>(), "text/plain", HttpStatusCode.Redirect);
@@ -90,16 +91,16 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private IHasHeaders GetHttpResult(IRequest requestContext, Stream content, string contentType, bool addCachePrevention, IDictionary<string, string> responseHeaders = null)
{
- var result = new StreamWriter(content, contentType, _logger);
+ var result = new StreamWriter(content, contentType);
if (responseHeaders == null)
{
responseHeaders = new Dictionary<string, string>();
}
- if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
+ if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string expires))
{
- responseHeaders["Expires"] = "-1";
+ responseHeaders[HeaderNames.Expires] = "-1";
}
AddResponseHeaders(result, responseHeaders);
@@ -131,7 +132,7 @@ namespace Emby.Server.Implementations.HttpServer
content = Array.Empty<byte>();
}
- result = new StreamWriter(content, contentType, contentLength, _logger);
+ result = new StreamWriter(content, contentType);
}
else
{
@@ -143,9 +144,9 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
- if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
+ if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
{
- responseHeaders["Expires"] = "-1";
+ responseHeaders[HeaderNames.Expires] = "-1";
}
AddResponseHeaders(result, responseHeaders);
@@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.HttpServer
bytes = Array.Empty<byte>();
}
- result = new StreamWriter(bytes, contentType, contentLength, _logger);
+ result = new StreamWriter(bytes, contentType);
}
else
{
@@ -187,9 +188,9 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>();
}
- if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string expires))
+ if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _))
{
- responseHeaders["Expires"] = "-1";
+ responseHeaders[HeaderNames.Expires] = "-1";
}
AddResponseHeaders(result, responseHeaders);
@@ -214,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer
responseHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
- responseHeaders["Expires"] = "-1";
+ responseHeaders[HeaderNames.Expires] = "-1";
return ToOptimizedResultInternal(requestContext, result, responseHeaders);
}
@@ -246,9 +247,9 @@ namespace Emby.Server.Implementations.HttpServer
private static string GetCompressionType(IRequest request)
{
- var acceptEncoding = request.Headers["Accept-Encoding"];
+ var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString();
- if (acceptEncoding != null)
+ if (string.IsNullOrEmpty(acceptEncoding))
{
//if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1)
// return "br";
@@ -277,9 +278,10 @@ namespace Emby.Server.Implementations.HttpServer
private object ToOptimizedResultInternal<T>(IRequest request, T dto, IDictionary<string, string> responseHeaders = null)
{
- var contentType = request.ResponseContentType;
+ // TODO: @bond use Span and .Equals
+ var contentType = request.ResponseContentType?.Split(';')[0].Trim().ToLowerInvariant();
- switch (GetRealContentType(contentType))
+ switch (contentType)
{
case "application/xml":
case "text/xml":
@@ -325,21 +327,21 @@ namespace Emby.Server.Implementations.HttpServer
}
content = Compress(content, requestedCompressionType);
- responseHeaders["Content-Encoding"] = requestedCompressionType;
+ responseHeaders[HeaderNames.ContentEncoding] = requestedCompressionType;
- responseHeaders["Vary"] = "Accept-Encoding";
+ responseHeaders[HeaderNames.Vary] = HeaderNames.AcceptEncoding;
var contentLength = content.Length;
if (isHeadRequest)
{
- var result = new StreamWriter(Array.Empty<byte>(), contentType, contentLength, _logger);
+ var result = new StreamWriter(Array.Empty<byte>(), contentType);
AddResponseHeaders(result, responseHeaders);
return result;
}
else
{
- var result = new StreamWriter(content, contentType, contentLength, _logger);
+ var result = new StreamWriter(content, contentType);
AddResponseHeaders(result, responseHeaders);
return result;
}
@@ -347,23 +349,19 @@ namespace Emby.Server.Implementations.HttpServer
private byte[] Compress(byte[] bytes, string compressionType)
{
- if (string.Equals(compressionType, "br", StringComparison.OrdinalIgnoreCase))
- return CompressBrotli(bytes);
-
if (string.Equals(compressionType, "deflate", StringComparison.OrdinalIgnoreCase))
+ {
return Deflate(bytes);
+ }
if (string.Equals(compressionType, "gzip", StringComparison.OrdinalIgnoreCase))
+ {
return GZip(bytes);
+ }
throw new NotSupportedException(compressionType);
}
- private byte[] CompressBrotli(byte[] bytes)
- {
- return _brotliCompressor.Compress(bytes);
- }
-
private static byte[] Deflate(byte[] bytes)
{
// In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream
@@ -390,13 +388,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- public static string GetRealContentType(string contentType)
- {
- return contentType == null
- ? null
- : contentType.Split(';')[0].ToLowerInvariant().Trim();
- }
-
private static string SerializeToXmlString(object from)
{
using (var ms = new MemoryStream())
@@ -424,12 +415,12 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
private object GetCachedResult(IRequest requestContext, IDictionary<string, string> responseHeaders, StaticResultOptions options)
{
- bool noCache = (requestContext.Headers.Get("Cache-Control") ?? string.Empty).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
+ bool noCache = (requestContext.Headers[HeaderNames.CacheControl].ToString()).IndexOf("no-cache", StringComparison.OrdinalIgnoreCase) != -1;
AddCachingHeaders(responseHeaders, options.CacheDuration, noCache, options.DateLastModified);
if (!noCache)
{
- DateTime.TryParse(requestContext.Headers.Get("If-Modified-Since"), out var ifModifiedSinceHeader);
+ DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
@@ -530,7 +521,7 @@ namespace Emby.Server.Implementations.HttpServer
options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var contentType = options.ContentType;
- if (!string.IsNullOrEmpty(requestContext.Headers.Get("If-Modified-Since")))
+ if (!StringValues.IsNullOrEmpty(requestContext.Headers[HeaderNames.IfModifiedSince]))
{
// See if the result is already cached in the browser
var result = GetCachedResult(requestContext, options.ResponseHeaders, options);
@@ -548,11 +539,11 @@ namespace Emby.Server.Implementations.HttpServer
AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified);
AddAgeHeader(responseHeaders, options.DateLastModified);
- var rangeHeader = requestContext.Headers.Get("Range");
+ var rangeHeader = requestContext.Headers[HeaderNames.Range];
if (!isHeadRequest && !string.IsNullOrEmpty(options.Path))
{
- var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem)
+ var hasHeaders = new FileWriter(options.Path, contentType, rangeHeader, _logger, _fileSystem, _streamHelper)
{
OnComplete = options.OnComplete,
OnError = options.OnError,
@@ -590,11 +581,6 @@ namespace Emby.Server.Implementations.HttpServer
}
else
{
- if (totalContentLength.HasValue)
- {
- responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture);
- }
-
if (isHeadRequest)
{
using (stream)
@@ -603,7 +589,7 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- var hasHeaders = new StreamWriter(stream, contentType, _logger)
+ var hasHeaders = new StreamWriter(stream, contentType)
{
OnComplete = options.OnComplete,
OnError = options.OnError
@@ -615,11 +601,6 @@ namespace Emby.Server.Implementations.HttpServer
}
/// <summary>
- /// The us culture
- /// </summary>
- private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- /// <summary>
/// Adds the caching responseHeaders.
/// </summary>
private void AddCachingHeaders(IDictionary<string, string> responseHeaders, TimeSpan? cacheDuration,
@@ -627,23 +608,23 @@ namespace Emby.Server.Implementations.HttpServer
{
if (noCache)
{
- responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate";
- responseHeaders["pragma"] = "no-cache, no-store, must-revalidate";
+ responseHeaders[HeaderNames.CacheControl] = "no-cache, no-store, must-revalidate";
+ responseHeaders[HeaderNames.Pragma] = "no-cache, no-store, must-revalidate";
return;
}
if (cacheDuration.HasValue)
{
- responseHeaders["Cache-Control"] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
+ responseHeaders[HeaderNames.CacheControl] = "public, max-age=" + cacheDuration.Value.TotalSeconds;
}
else
{
- responseHeaders["Cache-Control"] = "public";
+ responseHeaders[HeaderNames.CacheControl] = "public";
}
if (lastModifiedDate.HasValue)
{
- responseHeaders["Last-Modified"] = lastModifiedDate.ToString();
+ responseHeaders[HeaderNames.LastModified] = lastModifiedDate.ToString();
}
}
@@ -656,7 +637,7 @@ namespace Emby.Server.Implementations.HttpServer
{
if (lastDateModified.HasValue)
{
- responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
+ responseHeaders[HeaderNames.Age] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
}
}
@@ -714,9 +695,4 @@ namespace Emby.Server.Implementations.HttpServer
}
}
}
-
- public interface IBrotliCompressor
- {
- byte[] Compress(byte[] content);
- }
}
diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
index 835091361..005656d2c 100644
--- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs
+++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs
@@ -1,10 +1,9 @@
using System;
-using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Net;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.HttpServer
{
@@ -29,20 +28,10 @@ namespace Emby.Server.Implementations.HttpServer
Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
/// <summary>
- /// Gets or sets the web socket connecting.
- /// </summary>
- /// <value>The web socket connecting.</value>
- Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; }
-
- /// <summary>
- /// Starts this instance.
- /// </summary>
- /// <param name="urlPrefixes">The URL prefixes.</param>
- void Start(IEnumerable<string> urlPrefixes);
-
- /// <summary>
/// Stops this instance.
/// </summary>
Task Stop();
+
+ Task ProcessWebSocketRequest(HttpContext ctx);
}
}
diff --git a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs b/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
deleted file mode 100644
index d22d9db26..000000000
--- a/Emby.Server.Implementations/HttpServer/LoggerUtils.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System;
-using System.Globalization;
-using MediaBrowser.Model.Services;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Server.Implementations.HttpServer
-{
- public static class LoggerUtils
- {
- public static void LogRequest(ILogger logger, string url, string method, string userAgent, QueryParamCollection headers)
- {
- if (headers == null)
- {
- logger.LogInformation("{0} {1}. UserAgent: {2}", "HTTP " + method, url, userAgent ?? string.Empty);
- }
- else
- {
- var headerText = string.Empty;
- var index = 0;
-
- foreach (var i in headers)
- {
- if (index > 0)
- {
- headerText += ", ";
- }
-
- headerText += i.Name + "=" + i.Value;
-
- index++;
- }
-
- logger.LogInformation("HTTP {0} {1}. {2}", method, url, headerText);
- }
- }
-
- /// <summary>
- /// Logs the response.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="statusCode">The status code.</param>
- /// <param name="url">The URL.</param>
- /// <param name="endPoint">The end point.</param>
- /// <param name="duration">The duration.</param>
- public static void LogResponse(ILogger logger, int statusCode, string url, string endPoint, TimeSpan duration, QueryParamCollection headers)
- {
- var durationMs = duration.TotalMilliseconds;
- var logSuffix = durationMs >= 1000 && durationMs < 60000 ? "ms (slow)" : "ms";
-
- //var headerText = headers == null ? string.Empty : "Headers: " + string.Join(", ", headers.Where(i => i.Name.IndexOf("Access-", StringComparison.OrdinalIgnoreCase) == -1).Select(i => i.Name + "=" + i.Value).ToArray());
- var headerText = string.Empty;
- logger.LogInformation("HTTP Response {0} to {1}. Time: {2}{3}. {4} {5}", statusCode, endPoint, Convert.ToInt32(durationMs).ToString(CultureInfo.InvariantCulture), logSuffix, url, headerText);
- }
- }
-}
diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
index 891a76ec2..449159834 100644
--- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs
@@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
@@ -66,8 +67,8 @@ namespace Emby.Server.Implementations.HttpServer
this._logger = logger;
ContentType = contentType;
- Headers["Content-Type"] = contentType;
- Headers["Accept-Ranges"] = "bytes";
+ Headers[HeaderNames.ContentType] = contentType;
+ Headers[HeaderNames.AcceptRanges] = "bytes";
StatusCode = HttpStatusCode.PartialContent;
SetRangeValues(contentLength);
@@ -95,9 +96,7 @@ namespace Emby.Server.Implementations.HttpServer
RangeStart = requestedRange.Key;
RangeLength = 1 + RangeEnd - RangeStart;
- // Content-Length is the length of what we're serving, not the original content
- Headers["Content-Length"] = RangeLength.ToString(UsCulture);
- Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength);
+ Headers[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}";
if (RangeStart > 0 && SourceStream.CanSeek)
{
diff --git a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
index da2bf983a..a53d9bf0b 100644
--- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
+++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Text;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
@@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.HttpServer
public void FilterResponse(IRequest req, IResponse res, object dto)
{
// Try to prevent compatibility view
- res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-Length, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
+ res.AddHeader("Access-Control-Allow-Headers", "Accept, Accept-Language, Authorization, Cache-Control, Content-Disposition, Content-Encoding, Content-Language, Content-MD5, Content-Range, Content-Type, Date, Host, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, OriginToken, Pragma, Range, Slug, Transfer-Encoding, Want-Digest, X-MediaBrowser-Token, X-Emby-Authorization");
res.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
res.AddHeader("Access-Control-Allow-Origin", "*");
@@ -44,20 +45,19 @@ namespace Emby.Server.Implementations.HttpServer
if (dto is IHasHeaders hasHeaders)
{
- if (!hasHeaders.Headers.ContainsKey("Server"))
+ if (!hasHeaders.Headers.ContainsKey(HeaderNames.Server))
{
- hasHeaders.Headers["Server"] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50";
+ hasHeaders.Headers[HeaderNames.Server] = "Microsoft-NetCore/2.0, UPnP/1.0 DLNADOC/1.50";
}
// Content length has to be explicitly set on on HttpListenerResponse or it won't be happy
- if (hasHeaders.Headers.TryGetValue("Content-Length", out string contentLength)
+ if (hasHeaders.Headers.TryGetValue(HeaderNames.ContentLength, out string contentLength)
&& !string.IsNullOrEmpty(contentLength))
{
var length = long.Parse(contentLength, UsCulture);
if (length > 0)
{
- res.SetContentLength(length);
res.SendChunked = false;
}
}
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index cab41e65b..276312a30 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Services;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer.Security
{
@@ -176,7 +177,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
if (string.IsNullOrEmpty(auth))
{
- auth = httpReq.Headers["Authorization"];
+ auth = httpReq.Headers[HeaderNames.Authorization];
}
return GetAuthorization(auth);
diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs
index 3269d44cf..66a13e20d 100644
--- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs
+++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Services;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.HttpServer
{
@@ -14,8 +15,6 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class StreamWriter : IAsyncStreamWriter, IHasHeaders
{
- private ILogger Logger { get; set; }
-
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
/// <summary>
@@ -45,7 +44,7 @@ namespace Emby.Server.Implementations.HttpServer
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="logger">The logger.</param>
- public StreamWriter(Stream source, string contentType, ILogger logger)
+ public StreamWriter(Stream source, string contentType)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -53,14 +52,8 @@ namespace Emby.Server.Implementations.HttpServer
}
SourceStream = source;
- Logger = logger;
-
- Headers["Content-Type"] = contentType;
- if (source.CanSeek)
- {
- Headers["Content-Length"] = source.Length.ToString(UsCulture);
- }
+ Headers[HeaderNames.ContentType] = contentType;
}
/// <summary>
@@ -68,8 +61,7 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
/// <param name="source">The source.</param>
/// <param name="contentType">Type of the content.</param>
- /// <param name="logger">The logger.</param>
- public StreamWriter(byte[] source, string contentType, int contentLength, ILogger logger)
+ public StreamWriter(byte[] source, string contentType)
{
if (string.IsNullOrEmpty(contentType))
{
@@ -77,11 +69,8 @@ namespace Emby.Server.Implementations.HttpServer
}
SourceBytes = source;
- Logger = logger;
-
- Headers["Content-Type"] = contentType;
- Headers["Content-Length"] = contentLength.ToString(UsCulture);
+ Headers[HeaderNames.ContentType] = contentType;
}
public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken)
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index e9d0bac74..54a16040f 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using UtfUnknown;
@@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.HttpServer
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
- public QueryParamCollection QueryString { get; set; }
+ public IQueryCollection QueryString { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
@@ -101,12 +102,6 @@ namespace Emby.Server.Implementations.HttpServer
_socket = socket;
_socket.OnReceiveBytes = OnReceiveInternal;
- var memorySocket = socket as IMemoryWebSocket;
- if (memorySocket != null)
- {
- memorySocket.OnReceiveMemoryBytes = OnReceiveInternal;
- }
-
RemoteEndPoint = remoteEndPoint;
_logger = logger;
@@ -142,34 +137,6 @@ namespace Emby.Server.Implementations.HttpServer
}
}
- /// <summary>
- /// Called when [receive].
- /// </summary>
- /// <param name="memory">The memory block.</param>
- /// <param name="length">The length of the memory block.</param>
- private void OnReceiveInternal(Memory<byte> memory, int length)
- {
- LastActivityDate = DateTime.UtcNow;
-
- if (OnReceive == null)
- {
- return;
- }
-
- var bytes = memory.Slice(0, length).ToArray();
-
- var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
-
- if (string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase))
- {
- OnReceiveInternal(Encoding.UTF8.GetString(bytes, 0, bytes.Length));
- }
- else
- {
- OnReceiveInternal(Encoding.ASCII.GetString(bytes, 0, bytes.Length));
- }
- }
-
private void OnReceiveInternal(string message)
{
LastActivityDate = DateTime.UtcNow;
@@ -193,7 +160,7 @@ namespace Emby.Server.Implementations.HttpServer
var info = new WebSocketMessageInfo
{
MessageType = stub.MessageType,
- Data = stub.Data == null ? null : stub.Data.ToString(),
+ Data = stub.Data?.ToString(),
Connection = this
};
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index 3668f6a7a..73242d0ad 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -17,31 +17,23 @@ namespace Emby.Server.Implementations.IO
public class FileRefresher : IDisposable
{
private ILogger Logger { get; set; }
- private ITaskManager TaskManager { get; set; }
private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; }
- private readonly IFileSystem _fileSystem;
private readonly List<string> _affectedPaths = new List<string>();
private Timer _timer;
private readonly object _timerLock = new object();
public string Path { get; private set; }
public event EventHandler<EventArgs> Completed;
- private readonly IEnvironmentInfo _environmentInfo;
- private readonly ILibraryManager _libraryManager;
- public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, IEnvironmentInfo environmentInfo, ILibraryManager libraryManager1)
+ public FileRefresher(string path, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ILogger logger)
{
logger.LogDebug("New file refresher created for {0}", path);
Path = path;
- _fileSystem = fileSystem;
ConfigurationManager = configurationManager;
LibraryManager = libraryManager;
- TaskManager = taskManager;
Logger = logger;
- _environmentInfo = environmentInfo;
- _libraryManager = libraryManager1;
AddPath(path);
}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 607a4d333..d47342511 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.IO
/// <summary>
/// Any file name ending in any of these will be ignored by the watchers
/// </summary>
- private readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet<string> _alwaysIgnoreFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"small.jpg",
"albumart.jpg",
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.IO
"TempSBE"
};
- private readonly string[] _alwaysIgnoreSubstrings = new string[]
+ private static readonly string[] _alwaysIgnoreSubstrings = new string[]
{
// Synology
"eaDir",
@@ -53,7 +53,7 @@ namespace Emby.Server.Implementations.IO
".actors"
};
- private readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
+ private static readonly HashSet<string> _alwaysIgnoreExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
// thumbs.db
".db",
@@ -123,12 +123,6 @@ namespace Emby.Server.Implementations.IO
/// <value>The logger.</value>
private ILogger Logger { get; set; }
- /// <summary>
- /// Gets or sets the task manager.
- /// </summary>
- /// <value>The task manager.</value>
- private ITaskManager TaskManager { get; set; }
-
private ILibraryManager LibraryManager { get; set; }
private IServerConfigurationManager ConfigurationManager { get; set; }
@@ -140,19 +134,12 @@ namespace Emby.Server.Implementations.IO
/// </summary>
public LibraryMonitor(
ILoggerFactory loggerFactory,
- ITaskManager taskManager,
ILibraryManager libraryManager,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
IEnvironmentInfo environmentInfo)
{
- if (taskManager == null)
- {
- throw new ArgumentNullException(nameof(taskManager));
- }
-
LibraryManager = libraryManager;
- TaskManager = taskManager;
Logger = loggerFactory.CreateLogger(GetType().Name);
ConfigurationManager = configurationManager;
_fileSystem = fileSystem;
@@ -541,7 +528,7 @@ namespace Emby.Server.Implementations.IO
}
}
- var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _environmentInfo, LibraryManager);
+ var newRefresher = new FileRefresher(path, ConfigurationManager, LibraryManager, Logger);
newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher);
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 7c44878ec..421592fad 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
+using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
@@ -20,61 +21,27 @@ namespace Emby.Server.Implementations.IO
private readonly bool _supportsAsyncFileStreams;
private char[] _invalidFileNameChars;
private readonly List<IShortcutHandler> _shortcutHandlers = new List<IShortcutHandler>();
- private bool EnableSeparateFileAndDirectoryQueries;
- private string _tempPath;
+ private readonly string _tempPath;
- private IEnvironmentInfo _environmentInfo;
- private bool _isEnvironmentCaseInsensitive;
-
- private string _defaultDirectory;
+ private readonly IEnvironmentInfo _environmentInfo;
+ private readonly bool _isEnvironmentCaseInsensitive;
public ManagedFileSystem(
ILoggerFactory loggerFactory,
IEnvironmentInfo environmentInfo,
- string defaultDirectory,
- string tempPath,
- bool enableSeparateFileAndDirectoryQueries)
+ IApplicationPaths applicationPaths)
{
Logger = loggerFactory.CreateLogger("FileSystem");
_supportsAsyncFileStreams = true;
- _tempPath = tempPath;
+ _tempPath = applicationPaths.TempDirectory;
_environmentInfo = environmentInfo;
- _defaultDirectory = defaultDirectory;
-
- // On Linux with mono, this needs to be true or symbolic links are ignored
- EnableSeparateFileAndDirectoryQueries = enableSeparateFileAndDirectoryQueries;
SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows);
_isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
}
- public virtual string DefaultDirectory
- {
- get
- {
- var value = _defaultDirectory;
-
- if (!string.IsNullOrEmpty(value))
- {
- try
- {
- if (Directory.Exists(value))
- {
- return value;
- }
- }
- catch
- {
-
- }
- }
-
- return null;
- }
- }
-
public virtual void AddShortcutHandler(IShortcutHandler handler)
{
_shortcutHandlers.Add(handler);
@@ -718,7 +685,7 @@ namespace Emby.Server.Implementations.IO
SetAttributes(path, false, false);
File.Delete(path);
}
-
+
public virtual List<FileSystemMetadata> GetDrives()
{
// Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
@@ -743,20 +710,20 @@ namespace Emby.Server.Implementations.IO
return GetFiles(path, null, false, recursive);
}
- public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
+ public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
// On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
- if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
+ if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Count == 1)
{
return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption));
}
var files = new DirectoryInfo(path).EnumerateFiles("*", searchOption);
- if (extensions != null && extensions.Length > 0)
+ if (extensions != null && extensions.Count > 0)
{
files = files.Where(i =>
{
@@ -777,20 +744,15 @@ namespace Emby.Server.Implementations.IO
var directoryInfo = new DirectoryInfo(path);
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
- if (EnableSeparateFileAndDirectoryQueries)
- {
- return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
- .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
- }
-
- return ToMetadata(directoryInfo.EnumerateFileSystemInfos("*", searchOption));
+ return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption))
+ .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption)));
}
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
{
return infos.Select(GetFileSystemMetadata);
}
-
+
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
{
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
index 109c21f18..46f209b4b 100644
--- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
+++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs
@@ -20,6 +20,9 @@ namespace Emby.Server.Implementations.Images
public abstract class BaseDynamicImageProvider<T> : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider<T>, IHasOrder
where T : BaseItem
{
+ protected virtual IReadOnlyCollection<ImageType> SupportedImages { get; }
+ = new ImageType[] { ImageType.Primary };
+
protected IFileSystem FileSystem { get; private set; }
protected IProviderManager ProviderManager { get; private set; }
protected IApplicationPaths ApplicationPaths { get; private set; }
@@ -33,18 +36,7 @@ namespace Emby.Server.Implementations.Images
ImageProcessor = imageProcessor;
}
- protected virtual bool Supports(BaseItem item)
- {
- return true;
- }
-
- public virtual ImageType[] GetSupportedImages(BaseItem item)
- {
- return new ImageType[]
- {
- ImageType.Primary
- };
- }
+ protected virtual bool Supports(BaseItem _) => true;
public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
@@ -54,15 +46,14 @@ namespace Emby.Server.Implementations.Images
}
var updateType = ItemUpdateType.None;
- var supportedImages = GetSupportedImages(item);
- if (supportedImages.Contains(ImageType.Primary))
+ if (SupportedImages.Contains(ImageType.Primary))
{
var primaryResult = await FetchAsync(item, ImageType.Primary, options, cancellationToken).ConfigureAwait(false);
updateType = updateType | primaryResult;
}
- if (supportedImages.Contains(ImageType.Thumb))
+ if (SupportedImages.Contains(ImageType.Thumb))
{
var thumbResult = await FetchAsync(item, ImageType.Thumb, options, cancellationToken).ConfigureAwait(false);
updateType = updateType | thumbResult;
@@ -94,7 +85,7 @@ namespace Emby.Server.Implementations.Images
}
protected async Task<ItemUpdateType> FetchToFileInternal(BaseItem item,
- List<BaseItem> itemsWithImages,
+ IReadOnlyList<BaseItem> itemsWithImages,
ImageType imageType,
CancellationToken cancellationToken)
{
@@ -119,9 +110,9 @@ namespace Emby.Server.Implementations.Images
return ItemUpdateType.ImageUpdate;
}
- protected abstract List<BaseItem> GetItemsWithImages(BaseItem item);
+ protected abstract IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item);
- protected string CreateThumbCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath)
+ protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath)
{
return CreateCollage(primaryItem, items, outputPath, 640, 360);
}
@@ -132,38 +123,38 @@ namespace Emby.Server.Implementations.Images
.Select(i =>
{
var image = i.GetImageInfo(ImageType.Primary, 0);
-
if (image != null && image.IsLocalFile)
{
return image.Path;
}
- image = i.GetImageInfo(ImageType.Thumb, 0);
+ image = i.GetImageInfo(ImageType.Thumb, 0);
if (image != null && image.IsLocalFile)
{
return image.Path;
}
+
return null;
})
.Where(i => !string.IsNullOrEmpty(i));
}
- protected string CreatePosterCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath)
+ protected string CreatePosterCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath)
{
return CreateCollage(primaryItem, items, outputPath, 400, 600);
}
- protected string CreateSquareCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath)
+ protected string CreateSquareCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath)
{
return CreateCollage(primaryItem, items, outputPath, 600, 600);
}
- protected string CreateThumbCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath, int width, int height)
+ protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath, int width, int height)
{
return CreateCollage(primaryItem, items, outputPath, width, height);
}
- private string CreateCollage(BaseItem primaryItem, List<BaseItem> items, string outputPath, int width, int height)
+ private string CreateCollage(BaseItem primaryItem, IEnumerable<BaseItem> items, string outputPath, int width, int height)
{
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
@@ -192,7 +183,7 @@ namespace Emby.Server.Implementations.Images
public string Name => "Dynamic Image Provider";
protected virtual string CreateImage(BaseItem item,
- List<BaseItem> itemsWithImages,
+ IReadOnlyCollection<BaseItem> itemsWithImages,
string outputPathWithoutExtension,
ImageType imageType,
int imageIndex)
@@ -211,18 +202,15 @@ namespace Emby.Server.Implementations.Images
if (imageType == ImageType.Primary)
{
- if (item is UserView)
- {
- return CreateSquareCollage(item, itemsWithImages, outputPath);
- }
- if (item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum)
+ if (item is UserView || item is Playlist || item is MusicGenre || item is Genre || item is PhotoAlbum)
{
return CreateSquareCollage(item, itemsWithImages, outputPath);
}
+
return CreatePosterCollage(item, itemsWithImages, outputPath);
}
- throw new ArgumentException("Unexpected image type");
+ throw new ArgumentException("Unexpected image type", nameof(imageType));
}
protected virtual int MaxImageAgeDays => 7;
@@ -234,13 +222,11 @@ namespace Emby.Server.Implementations.Images
return false;
}
- var supportedImages = GetSupportedImages(item);
-
- if (supportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary))
+ if (SupportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary))
{
return true;
}
- if (supportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
+ if (SupportedImages.Contains(ImageType.Thumb) && HasChanged(item, ImageType.Thumb))
{
return true;
}
@@ -285,7 +271,7 @@ namespace Emby.Server.Implementations.Images
public int Order => 0;
- protected string CreateSingleImage(List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType)
+ protected string CreateSingleImage(IEnumerable<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType)
{
var image = itemsWithImages
.Where(i => i.HasImage(imageType) && i.GetImageInfo(imageType, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(imageType)))
diff --git a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index 80f746c7a..c644d13ea 100644
--- a/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/Emby.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -1,5 +1,4 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Controller.Entities;
@@ -7,7 +6,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library
{
@@ -16,16 +14,14 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
- private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
private bool _ignoreDotPrefix;
/// <summary>
/// Any folder named in this list will be ignored - can be added to at runtime for extensibility
/// </summary>
- public static readonly Dictionary<string, string> IgnoreFolders = new List<string>
+ public static readonly string[] IgnoreFolders =
{
"metadata",
"ps3_update",
@@ -50,13 +46,11 @@ namespace Emby.Server.Implementations.Library
// macos
".AppleDouble"
- }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+ };
- public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager, ILogger logger)
+ public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
{
- _fileSystem = fileSystem;
_libraryManager = libraryManager;
- _logger = logger;
_ignoreDotPrefix = Environment.OSVersion.Platform != PlatformID.Win32NT;
}
@@ -117,7 +111,7 @@ namespace Emby.Server.Implementations.Library
if (fileInfo.IsDirectory)
{
// Ignore any folders in our list
- if (IgnoreFolders.ContainsKey(filename))
+ if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return true;
}
diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
index 4013ac0c8..3ec1f81d3 100644
--- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
+++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Controller.Authentication;
@@ -18,20 +19,64 @@ namespace Emby.Server.Implementations.Library
public string Name => "Default";
public bool IsEnabled => true;
-
+
+ // This is dumb and an artifact of the backwards way auth providers were designed.
+ // This version of authenticate was never meant to be called, but needs to be here for interface compat
+ // Only the providers that don't provide local user support use this
public Task<ProviderAuthenticationResult> Authenticate(string username, string password)
{
throw new NotImplementedException();
}
-
+
+ // This is the verson that we need to use for local users. Because reasons.
public Task<ProviderAuthenticationResult> Authenticate(string username, string password, User resolvedUser)
{
+ bool success = false;
if (resolvedUser == null)
{
throw new Exception("Invalid username or password");
}
- var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
+ // As long as jellyfin supports passwordless users, we need this little block here to accomodate
+ if (IsPasswordEmpty(resolvedUser, password))
+ {
+ return Task.FromResult(new ProviderAuthenticationResult
+ {
+ Username = username
+ });
+ }
+
+ ConvertPasswordFormat(resolvedUser);
+ byte[] passwordbytes = Encoding.UTF8.GetBytes(password);
+
+ PasswordHash readyHash = new PasswordHash(resolvedUser.Password);
+ byte[] calculatedHash;
+ string calculatedHashString;
+ if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id))
+ {
+ if (string.IsNullOrEmpty(readyHash.Salt))
+ {
+ calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes);
+ calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
+ }
+ else
+ {
+ calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes);
+ calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty);
+ }
+
+ if (calculatedHashString == readyHash.Hash)
+ {
+ success = true;
+ // throw new Exception("Invalid username or password");
+ }
+ }
+ else
+ {
+ throw new Exception(string.Format($"Requested crypto method not available in provider: {readyHash.Id}"));
+ }
+
+ // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase);
if (!success)
{
@@ -44,46 +89,86 @@ namespace Emby.Server.Implementations.Library
});
}
+ // This allows us to move passwords forward to the newformat without breaking. They are still insecure, unsalted, and dumb before a password change
+ // but at least they are in the new format.
+ private void ConvertPasswordFormat(User user)
+ {
+ if (string.IsNullOrEmpty(user.Password))
+ {
+ return;
+ }
+
+ if (!user.Password.Contains("$"))
+ {
+ string hash = user.Password;
+ user.Password = string.Format("$SHA1${0}", hash);
+ }
+
+ if (user.EasyPassword != null && !user.EasyPassword.Contains("$"))
+ {
+ string hash = user.EasyPassword;
+ user.EasyPassword = string.Format("$SHA1${0}", hash);
+ }
+ }
+
public Task<bool> HasPassword(User user)
{
var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user));
return Task.FromResult(hasConfiguredPassword);
}
- private bool IsPasswordEmpty(User user, string passwordHash)
+ private bool IsPasswordEmpty(User user, string password)
{
- return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase);
+ return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password));
}
public Task ChangePassword(User user, string newPassword)
{
- string newPasswordHash = null;
+ ConvertPasswordFormat(user);
+ // This is needed to support changing a no password user to a password user
+ if (string.IsNullOrEmpty(user.Password))
+ {
+ PasswordHash newPasswordHash = new PasswordHash(_cryptographyProvider);
+ newPasswordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
+ newPasswordHash.Salt = PasswordHash.ConvertToByteString(newPasswordHash.SaltBytes);
+ newPasswordHash.Id = _cryptographyProvider.DefaultHashMethod;
+ newPasswordHash.Hash = GetHashedStringChangeAuth(newPassword, newPasswordHash);
+ user.Password = newPasswordHash.ToString();
+ return Task.CompletedTask;
+ }
- if (newPassword != null)
+ PasswordHash passwordHash = new PasswordHash(user.Password);
+ if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt))
{
- newPasswordHash = GetHashedString(user, newPassword);
+ passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt();
+ passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes);
+ passwordHash.Id = _cryptographyProvider.DefaultHashMethod;
+ passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash);
+ }
+ else if (newPassword != null)
+ {
+ passwordHash.Hash = GetHashedString(user, newPassword);
}
- if (string.IsNullOrWhiteSpace(newPasswordHash))
+ if (string.IsNullOrWhiteSpace(passwordHash.Hash))
{
- throw new ArgumentNullException(nameof(newPasswordHash));
+ throw new ArgumentNullException(nameof(passwordHash.Hash));
}
- user.Password = newPasswordHash;
+ user.Password = passwordHash.ToString();
return Task.CompletedTask;
}
public string GetPasswordHash(User user)
{
- return string.IsNullOrEmpty(user.Password)
- ? GetEmptyHashedString(user)
- : user.Password;
+ return user.Password;
}
- public string GetEmptyHashedString(User user)
+ public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash)
{
- return GetHashedString(user, string.Empty);
+ passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword);
+ return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
}
/// <summary>
@@ -91,14 +176,28 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public string GetHashedString(User user, string str)
{
- var salt = user.Salt;
- if (salt != null)
+ PasswordHash passwordHash;
+ if (string.IsNullOrEmpty(user.Password))
+ {
+ passwordHash = new PasswordHash(_cryptographyProvider);
+ }
+ else
{
- // return BCrypt.HashPassword(str, salt);
+ ConvertPasswordFormat(user);
+ passwordHash = new PasswordHash(user.Password);
}
- // legacy
- return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty);
+ if (passwordHash.SaltBytes != null)
+ {
+ // the password is modern format with PBKDF and we should take advantage of that
+ passwordHash.HashBytes = Encoding.UTF8.GetBytes(str);
+ return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash));
+ }
+ else
+ {
+ // the password has no salt and should be called with the older method for safety
+ return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str)));
+ }
}
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 064006ebd..6591d54c5 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -278,6 +278,7 @@ namespace Emby.Server.Implementations.Library
{
throw new ArgumentNullException(nameof(item));
}
+
if (item is IItemByName)
{
if (!(item is MusicArtist))
@@ -285,18 +286,7 @@ namespace Emby.Server.Implementations.Library
return;
}
}
-
- else if (item.IsFolder)
- {
- //if (!(item is ICollectionFolder) && !(item is UserView) && !(item is Channel) && !(item is AggregateFolder))
- //{
- // if (item.SourceType != SourceType.Library)
- // {
- // return;
- // }
- //}
- }
- else
+ else if (!item.IsFolder)
{
if (!(item is Video) && !(item is LiveTvChannel))
{
@@ -371,19 +361,20 @@ namespace Emby.Server.Implementations.Library
foreach (var metadataPath in GetMetadataPaths(item, children))
{
- _logger.LogDebug("Deleting path {0}", metadataPath);
+ if (!Directory.Exists(metadataPath))
+ {
+ continue;
+ }
+
+ _logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
try
{
Directory.Delete(metadataPath, true);
}
- catch (IOException)
- {
-
- }
catch (Exception ex)
{
- _logger.LogError(ex, "Error deleting {metadataPath}", metadataPath);
+ _logger.LogError(ex, "Error deleting {MetadataPath}", metadataPath);
}
}
@@ -986,7 +977,7 @@ namespace Emby.Server.Implementations.Library
// Ensure the location is available.
Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
- return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
+ return new PeopleValidator(this, _logger, _fileSystem).ValidatePeople(cancellationToken, progress);
}
/// <summary>
@@ -1225,9 +1216,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException">id</exception>
public BaseItem GetItemById(Guid id)
{
- if (id.Equals(Guid.Empty))
+ if (id == Guid.Empty)
{
- throw new ArgumentNullException(nameof(id));
+ throw new ArgumentException(nameof(id), "Guid can't be empty");
}
if (LibraryItemsCache.TryGetValue(id, out BaseItem item))
@@ -1237,8 +1228,6 @@ namespace Emby.Server.Implementations.Library
item = RetrieveItem(id);
- //_logger.LogDebug("GetitemById {0}", id);
-
if (item != null)
{
RegisterItem(item);
@@ -1333,7 +1322,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetItemIdsList(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1344,7 +1333,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetStudios(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1355,7 +1344,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetGenres(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1366,7 +1355,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetMusicGenres(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1377,7 +1366,7 @@ namespace Emby.Server.Implementations.Library
return ItemRepository.GetAllArtists(query);
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1421,7 +1410,7 @@ namespace Emby.Server.Implementations.Library
}
}
- public QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1808,18 +1797,16 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
public void CreateItems(IEnumerable<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{
- var list = items.ToList();
-
- ItemRepository.SaveItems(list, cancellationToken);
+ ItemRepository.SaveItems(items, cancellationToken);
- foreach (var item in list)
+ foreach (var item in items)
{
RegisterItem(item);
}
if (ItemAdded != null)
{
- foreach (var item in list)
+ foreach (var item in items)
{
// With the live tv guide this just creates too much noise
if (item.SourceType != SourceType.Library)
@@ -1853,7 +1840,7 @@ namespace Emby.Server.Implementations.Library
/// <summary>
/// Updates the item.
/// </summary>
- public void UpdateItems(List<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
+ public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
foreach (var item in items)
{
@@ -1908,7 +1895,7 @@ namespace Emby.Server.Implementations.Library
/// <returns>Task.</returns>
public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
{
- UpdateItems(new List<BaseItem> { item }, parent, updateReason, cancellationToken);
+ UpdateItems(new [] { item }, parent, updateReason, cancellationToken);
}
/// <summary>
@@ -2005,9 +1992,7 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault();
}
- var options = collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
-
- return options;
+ return collectionFolder == null ? new LibraryOptions() : collectionFolder.GetLibraryOptions();
}
public string GetContentType(BaseItem item)
@@ -2017,11 +2002,13 @@ namespace Emby.Server.Implementations.Library
{
return configuredContentType;
}
+
configuredContentType = GetConfiguredContentType(item, true);
if (!string.IsNullOrEmpty(configuredContentType))
{
return configuredContentType;
}
+
return GetInheritedContentType(item);
}
@@ -2056,6 +2043,7 @@ namespace Emby.Server.Implementations.Library
{
return collectionFolder.CollectionType;
}
+
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
}
@@ -2066,6 +2054,7 @@ namespace Emby.Server.Implementations.Library
{
return nameValuePair.Value;
}
+
return null;
}
@@ -2108,9 +2097,9 @@ namespace Emby.Server.Implementations.Library
string viewType,
string sortName)
{
- var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views");
-
- path = Path.Combine(path, _fileSystem.GetValidFilename(viewType));
+ var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath,
+ "views",
+ _fileSystem.GetValidFilename(viewType));
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
@@ -2543,7 +2532,7 @@ namespace Emby.Server.Implementations.Library
var resolvers = new IItemResolver[]
{
- new GenericVideoResolver<Trailer>(this, _fileSystem)
+ new GenericVideoResolver<Trailer>(this)
};
return ResolvePaths(files, directoryService, null, new LibraryOptions(), null, resolvers)
diff --git a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
index d992f8d03..541b13cbe 100644
--- a/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/BaseVideoResolver.cs
@@ -6,7 +6,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -18,11 +17,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
where T : Video, new()
{
protected readonly ILibraryManager LibraryManager;
- protected readonly IFileSystem FileSystem;
- protected BaseVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem)
+ protected BaseVideoResolver(ILibraryManager libraryManager)
{
- FileSystem = fileSystem;
LibraryManager = libraryManager;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 472a3f105..848563679 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -548,7 +548,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private IImageProcessor _imageProcessor;
- public MovieResolver(ILibraryManager libraryManager, IFileSystem fileSystem, IImageProcessor imageProcessor) : base(libraryManager, fileSystem)
+ public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
+ : base(libraryManager)
{
_imageProcessor = imageProcessor;
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index a3298c580..db270c398 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
@@ -15,13 +14,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
- private readonly IFileSystem _fileSystem;
- public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager, IFileSystem fileSystem)
+ public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
- _fileSystem = fileSystem;
}
/// <summary>
@@ -113,8 +110,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
return false;
}
- return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'));
+ return imageProcessor.SupportedInputFormats.Contains(Path.GetExtension(path).TrimStart('.'), StringComparer.Ordinal);
}
-
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
index fed0904d1..a6d18c9b5 100644
--- a/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
@@ -3,7 +3,6 @@ using System.Linq;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers.TV
{
@@ -74,7 +73,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null;
}
- public EpisodeResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ public EpisodeResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
{
}
}
diff --git a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
index 60752a85d..68d5d8b2d 100644
--- a/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/VideoResolver.cs
@@ -1,13 +1,13 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.IO;
namespace Emby.Server.Implementations.Library.Resolvers
{
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new()
{
- public GenericVideoResolver(ILibraryManager libraryManager, IFileSystem fileSystem) : base(libraryManager, fileSystem)
+ public GenericVideoResolver(ILibraryManager libraryManager)
+ : base(libraryManager)
{
}
}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 3ff84382f..efb1ef4a5 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Events;
@@ -74,7 +75,6 @@ namespace Emby.Server.Implementations.Library
private readonly Func<IDtoService> _dtoServiceFactory;
private readonly IServerApplicationHost _appHost;
private readonly IFileSystem _fileSystem;
- private readonly ICryptoProvider _cryptographyProvider;
private IAuthenticationProvider[] _authenticationProviders;
private DefaultAuthenticationProvider _defaultAuthenticationProvider;
@@ -89,8 +89,7 @@ namespace Emby.Server.Implementations.Library
Func<IDtoService> dtoServiceFactory,
IServerApplicationHost appHost,
IJsonSerializer jsonSerializer,
- IFileSystem fileSystem,
- ICryptoProvider cryptographyProvider)
+ IFileSystem fileSystem)
{
_logger = loggerFactory.CreateLogger(nameof(UserManager));
UserRepository = userRepository;
@@ -101,7 +100,6 @@ namespace Emby.Server.Implementations.Library
_appHost = appHost;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
- _cryptographyProvider = cryptographyProvider;
ConfigurationManager = configurationManager;
_users = Array.Empty<User>();
@@ -171,9 +169,9 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException"></exception>
public User GetUserById(Guid id)
{
- if (id.Equals(Guid.Empty))
+ if (id == Guid.Empty)
{
- throw new ArgumentNullException(nameof(id));
+ throw new ArgumentException(nameof(id), "Guid can't be empty");
}
return Users.FirstOrDefault(u => u.Id == id);
@@ -216,22 +214,17 @@ namespace Emby.Server.Implementations.Library
}
}
- public bool IsValidUsername(string username)
+ public static bool IsValidUsername(string username)
{
- // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
- foreach (var currentChar in username)
- {
- if (!IsValidUsernameCharacter(currentChar))
- {
- return false;
- }
- }
- return true;
+ //This is some regex that matches only on unicode "word" characters, as well as -, _ and @
+ //In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
+ // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)
+ return Regex.IsMatch(username, "^[\\w-'._@]*$");
}
private static bool IsValidUsernameCharacter(char i)
{
- return !char.Equals(i, '<') && !char.Equals(i, '>');
+ return IsValidUsername(i.ToString());
}
public string MakeValidUsername(string username)
@@ -478,15 +471,10 @@ namespace Emby.Server.Implementations.Library
private string GetLocalPasswordHash(User user)
{
return string.IsNullOrEmpty(user.EasyPassword)
- ? _defaultAuthenticationProvider.GetEmptyHashedString(user)
+ ? null
: user.EasyPassword;
}
- private bool IsPasswordEmpty(User user, string passwordHash)
- {
- return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase);
- }
-
/// <summary>
/// Loads the users from the repository
/// </summary>
@@ -529,14 +517,14 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(user));
}
- var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
- var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user));
+ bool hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result;
+ bool hasConfiguredEasyPassword = string.IsNullOrEmpty(GetLocalPasswordHash(user));
- var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
+ bool hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ?
hasConfiguredEasyPassword :
hasConfiguredPassword;
- var dto = new UserDto
+ UserDto dto = new UserDto
{
Id = user.Id,
Name = user.Name,
@@ -555,7 +543,7 @@ namespace Emby.Server.Implementations.Library
dto.EnableAutoLogin = true;
}
- var image = user.GetImageInfo(ImageType.Primary, 0);
+ ItemImageInfo image = user.GetImageInfo(ImageType.Primary, 0);
if (image != null)
{
@@ -691,7 +679,7 @@ namespace Emby.Server.Implementations.Library
if (!IsValidUsername(name))
{
- throw new ArgumentException("Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
+ throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
}
if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
index 0ea543ba0..7899cf01b 100644
--- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -24,7 +24,6 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary>
private readonly ILogger _logger;
- private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
/// <summary>
@@ -32,11 +31,10 @@ namespace Emby.Server.Implementations.Library.Validators
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
- public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
+ public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
_logger = logger;
- _config = config;
_fileSystem = fileSystem;
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 84ca130b7..f424bdf5c 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -105,8 +105,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_mediaSourceManager = mediaSourceManager;
_streamHelper = streamHelper;
- _seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
- _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
+ _seriesTimerProvider = new SeriesTimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers.json"));
+ _timerProvider = new TimerManager(jsonSerializer, _logger, Path.Combine(DataPath, "timers.json"), _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -1708,7 +1708,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
{
- return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _httpClient, _processFactory, _config, _assemblyInfo);
+ return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
}
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index eed239514..9a9bae215 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -7,7 +7,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
@@ -17,7 +16,6 @@ using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Reflection;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -27,7 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
- private readonly IHttpClient _httpClient;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerApplicationPaths _appPaths;
private bool _hasExited;
@@ -38,19 +35,23 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private readonly IJsonSerializer _json;
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
private readonly IServerConfigurationManager _config;
- private readonly IAssemblyInfo _assemblyInfo;
- public EncodedRecorder(ILogger logger, IFileSystem fileSystem, IMediaEncoder mediaEncoder, IServerApplicationPaths appPaths, IJsonSerializer json, IHttpClient httpClient, IProcessFactory processFactory, IServerConfigurationManager config, IAssemblyInfo assemblyInfo)
+ public EncodedRecorder(
+ ILogger logger,
+ IFileSystem fileSystem,
+ IMediaEncoder mediaEncoder,
+ IServerApplicationPaths appPaths,
+ IJsonSerializer json,
+ IProcessFactory processFactory,
+ IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_appPaths = appPaths;
_json = json;
- _httpClient = httpClient;
_processFactory = processFactory;
_config = config;
- _assemblyInfo = assemblyInfo;
}
private static bool CopySubtitles => false;
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index 6b02eaea8..9c45ee36a 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -17,15 +16,13 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
protected readonly ILogger Logger;
private readonly string _dataPath;
protected readonly Func<T, T, bool> EqualityComparer;
- private readonly IFileSystem _fileSystem;
- public ItemDataProvider(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
+ public ItemDataProvider(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, Func<T, T, bool> equalityComparer)
{
Logger = logger;
_dataPath = dataPath;
EqualityComparer = equalityComparer;
_jsonSerializer = jsonSerializer;
- _fileSystem = fileSystem;
}
public IReadOnlyList<T> GetAll()
@@ -34,31 +31,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
if (_items == null)
{
+ if (!File.Exists(_dataPath))
+ {
+ return new List<T>();
+ }
+
Logger.LogInformation("Loading live tv data from {0}", _dataPath);
_items = GetItemsFromFile(_dataPath);
}
+
return _items.ToList();
}
}
private List<T> GetItemsFromFile(string path)
{
- var jsonFile = path + ".json";
-
try
{
- return _jsonSerializer.DeserializeFromFile<List<T>>(jsonFile) ?? new List<T>();
- }
- catch (FileNotFoundException)
- {
- }
- catch (IOException)
- {
+ return _jsonSerializer.DeserializeFromFile<List<T>>(path);
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error deserializing {jsonFile}", jsonFile);
+ Logger.LogError(ex, "Error deserializing {Path}", path);
}
+
return new List<T>();
}
@@ -69,12 +65,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException(nameof(newList));
}
- var file = _dataPath + ".json";
- Directory.CreateDirectory(Path.GetDirectoryName(file));
+ Directory.CreateDirectory(Path.GetDirectoryName(_dataPath));
lock (_fileDataLock)
{
- _jsonSerializer.SerializeToFile(newList, file);
+ _jsonSerializer.SerializeToFile(newList, _dataPath);
_items = newList;
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
index d2ad65a1e..520b44404 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/SeriesTimerManager.cs
@@ -1,6 +1,5 @@
using System;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -8,8 +7,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
public class SeriesTimerManager : ItemDataProvider<SeriesTimerInfo>
{
- public SeriesTimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
- : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public SeriesTimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath)
+ : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 1dcb02f43..3c807a8ea 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Threading;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Events;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -19,8 +18,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
- public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
- : base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
+ public TimerManager(IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
+ : base(jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
_logger = logger1;
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 0bbffb824..4137760d0 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -17,6 +17,7 @@ using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.LiveTv.Listings
{
@@ -638,7 +639,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
#if NETSTANDARD2_0
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
- options.RequestHeaders["Accept-Encoding"] = "deflate";
+ options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
}
#endif
@@ -676,7 +677,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
#if NETSTANDARD2_0
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
- options.RequestHeaders["Accept-Encoding"] = "deflate";
+ options.RequestHeaders[HeaderNames.AcceptEncoding] = "deflate";
}
#endif
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index a36302876..f7ef16fb0 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -184,7 +184,7 @@ namespace Emby.Server.Implementations.LiveTv
public QueryResult<BaseItem> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
{
- var user = query.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(query.UserId);
+ var user = query.UserId == Guid.Empty ? null : _userManager.GetUserById(query.UserId);
var topFolder = GetInternalLiveTvFolder(cancellationToken);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 6d1eff187..715f600a1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -9,7 +9,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
@@ -23,18 +22,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
protected readonly IServerConfigurationManager Config;
protected readonly ILogger Logger;
protected IJsonSerializer JsonSerializer;
- protected readonly IMediaEncoder MediaEncoder;
protected readonly IFileSystem FileSystem;
private readonly ConcurrentDictionary<string, ChannelCache> _channelCache =
new ConcurrentDictionary<string, ChannelCache>(StringComparer.OrdinalIgnoreCase);
- protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem)
+ protected BaseTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
{
Config = config;
Logger = logger;
JsonSerializer = jsonSerializer;
- MediaEncoder = mediaEncoder;
FileSystem = fileSystem;
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 77b09a83d..24b100edd 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -31,15 +31,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private readonly IServerApplicationHost _appHost;
private readonly ISocketFactory _socketFactory;
private readonly INetworkManager _networkManager;
- private readonly IEnvironmentInfo _environment;
- public HdHomerunHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+ public HdHomerunHost(
+ IServerConfigurationManager config,
+ ILogger logger,
+ IJsonSerializer jsonSerializer,
+ IFileSystem fileSystem,
+ IHttpClient httpClient,
+ IServerApplicationHost appHost,
+ ISocketFactory socketFactory,
+ INetworkManager networkManager)
+ : base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
_socketFactory = socketFactory;
_networkManager = networkManager;
- _environment = environment;
}
public string Name => "HD Homerun";
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 638796e2e..588dcb843 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -19,6 +19,7 @@ using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
@@ -26,15 +27,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- private readonly IEnvironmentInfo _environment;
private readonly INetworkManager _networkManager;
private readonly IMediaSourceManager _mediaSourceManager;
- public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment, INetworkManager networkManager) : base(config, logger, jsonSerializer, mediaEncoder, fileSystem)
+ public M3UTunerHost(IServerConfigurationManager config, IMediaSourceManager mediaSourceManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, INetworkManager networkManager)
+ : base(config, logger, jsonSerializer, fileSystem)
{
_httpClient = httpClient;
_appHost = appHost;
- _environment = environment;
_networkManager = networkManager;
_mediaSourceManager = mediaSourceManager;
}
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
var channelIdPrefix = GetFullChannelIdPrefix(info);
- var result = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
+ var result = await new M3uParser(Logger, _httpClient, _appHost).Parse(info.Url, channelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
return result.Cast<ChannelInfo>().ToList();
}
@@ -115,7 +115,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
- using (var stream = await new M3uParser(Logger, FileSystem, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
+ using (var stream = await new M3uParser(Logger, _httpClient, _appHost).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
}
@@ -146,7 +146,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (protocol == MediaProtocol.Http)
{
// Use user-defined user-agent. If there isn't one, make it look like a browser.
- httpHeaders["User-Agent"] = string.IsNullOrWhiteSpace(info.UserAgent) ?
+ httpHeaders[HeaderNames.UserAgent] = string.IsNullOrWhiteSpace(info.UserAgent) ?
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.85 Safari/537.36" :
info.UserAgent;
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index d2a835b85..ad124bb0f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -19,14 +19,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public class M3uParser
{
private readonly ILogger _logger;
- private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
private readonly IServerApplicationHost _appHost;
- public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost)
+ public M3uParser(ILogger logger, IHttpClient httpClient, IServerApplicationHost appHost)
{
_logger = logger;
- _fileSystem = fileSystem;
_httpClient = httpClient;
_appHost = appHost;
}
diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs
index 31217730b..762649b71 100644
--- a/Emby.Server.Implementations/Localization/LocalizationManager.cs
+++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs
@@ -62,10 +62,6 @@ namespace Emby.Server.Implementations.Localization
{
const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings.";
- Directory.CreateDirectory(LocalizationPath);
-
- var existingFiles = GetRatingsFiles(LocalizationPath).Select(Path.GetFileName);
-
// Extract from the assembly
foreach (var resource in _assembly.GetManifestResourceNames())
{
@@ -74,100 +70,41 @@ namespace Emby.Server.Implementations.Localization
continue;
}
- string filename = "ratings-" + resource.Substring(ratingsResource.Length);
-
- if (existingFiles.Contains(filename))
- {
- continue;
- }
+ string countryCode = resource.Substring(ratingsResource.Length, 2);
+ var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
- using (var stream = _assembly.GetManifestResourceStream(resource))
+ using (var str = _assembly.GetManifestResourceStream(resource))
+ using (var reader = new StreamReader(str))
{
- string target = Path.Combine(LocalizationPath, filename);
- _logger.LogInformation("Extracting ratings to {0}", target);
-
- using (var fs = _fileSystem.GetFileStream(target, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ string line;
+ while ((line = await reader.ReadLineAsync()) != null)
{
- await stream.CopyToAsync(fs);
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ string[] parts = line.Split(',');
+ if (parts.Length == 2
+ && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value))
+ {
+ dict.Add(parts[0], new ParentalRating { Name = parts[0], Value = value });
+ }
+#if DEBUG
+ else
+ {
+ _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
+ }
+#endif
}
}
- }
- foreach (var file in GetRatingsFiles(LocalizationPath))
- {
- await LoadRatings(file);
+ _allParentalRatings[countryCode] = dict;
}
- LoadAdditionalRatings();
-
await LoadCultures();
}
- private void LoadAdditionalRatings()
- {
- LoadRatings("au", new[]
- {
- new ParentalRating("AU-G", 1),
- new ParentalRating("AU-PG", 5),
- new ParentalRating("AU-M", 6),
- new ParentalRating("AU-MA15+", 7),
- new ParentalRating("AU-M15+", 8),
- new ParentalRating("AU-R18+", 9),
- new ParentalRating("AU-X18+", 10),
- new ParentalRating("AU-RC", 11)
- });
-
- LoadRatings("be", new[]
- {
- new ParentalRating("BE-AL", 1),
- new ParentalRating("BE-MG6", 2),
- new ParentalRating("BE-6", 3),
- new ParentalRating("BE-9", 5),
- new ParentalRating("BE-12", 6),
- new ParentalRating("BE-16", 8)
- });
-
- LoadRatings("de", new[]
- {
- new ParentalRating("DE-0", 1),
- new ParentalRating("FSK-0", 1),
- new ParentalRating("DE-6", 5),
- new ParentalRating("FSK-6", 5),
- new ParentalRating("DE-12", 7),
- new ParentalRating("FSK-12", 7),
- new ParentalRating("DE-16", 8),
- new ParentalRating("FSK-16", 8),
- new ParentalRating("DE-18", 9),
- new ParentalRating("FSK-18", 9)
- });
-
- LoadRatings("ru", new[]
- {
- new ParentalRating("RU-0+", 1),
- new ParentalRating("RU-6+", 3),
- new ParentalRating("RU-12+", 7),
- new ParentalRating("RU-16+", 9),
- new ParentalRating("RU-18+", 10)
- });
- }
-
- private void LoadRatings(string country, ParentalRating[] ratings)
- {
- _allParentalRatings[country] = ratings.ToDictionary(i => i.Name);
- }
-
- private IEnumerable<string> GetRatingsFiles(string directory)
- => _fileSystem.GetFilePaths(directory, false)
- .Where(i => string.Equals(Path.GetExtension(i), ".csv", StringComparison.OrdinalIgnoreCase))
- .Where(i => Path.GetFileName(i).StartsWith("ratings-", StringComparison.OrdinalIgnoreCase));
-
- /// <summary>
- /// Gets the localization path.
- /// </summary>
- /// <value>The localization path.</value>
- public string LocalizationPath
- => Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization");
-
public string NormalizeFormKD(string text)
=> text.Normalize(NormalizationForm.FormKD);
@@ -288,47 +225,6 @@ namespace Emby.Server.Implementations.Localization
return value;
}
- /// <summary>
- /// Loads the ratings.
- /// </summary>
- /// <param name="file">The file.</param>
- /// <returns>Dictionary{System.StringParentalRating}.</returns>
- private async Task LoadRatings(string file)
- {
- Dictionary<string, ParentalRating> dict
- = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
-
- using (var str = File.OpenRead(file))
- using (var reader = new StreamReader(str))
- {
- string line;
- while ((line = await reader.ReadLineAsync()) != null)
- {
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- string[] parts = line.Split(',');
- if (parts.Length == 2
- && int.TryParse(parts[1], NumberStyles.Integer, UsCulture, out var value))
- {
- dict.Add(parts[0], (new ParentalRating { Name = parts[0], Value = value }));
- }
-#if DEBUG
- else
- {
- _logger.LogWarning("Misformed line in {Path}", file);
- }
-#endif
- }
- }
-
- var countryCode = Path.GetFileNameWithoutExtension(file).Split('-')[1];
-
- _allParentalRatings[countryCode] = dict;
- }
-
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
/// <summary>
diff --git a/Emby.Server.Implementations/Localization/Ratings/au.csv b/Emby.Server.Implementations/Localization/Ratings/au.csv
new file mode 100644
index 000000000..940375e26
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/au.csv
@@ -0,0 +1,8 @@
+AU-G,1
+AU-PG,5
+AU-M,6
+AU-MA15+,7
+AU-M15+,8
+AU-R18+,9
+AU-X18+,10
+AU-RC,11
diff --git a/Emby.Server.Implementations/Localization/Ratings/be.csv b/Emby.Server.Implementations/Localization/Ratings/be.csv
new file mode 100644
index 000000000..d3937caf7
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/be.csv
@@ -0,0 +1,6 @@
+BE-AL,1
+BE-MG6,2
+BE-6,3
+BE-9,5
+BE-12,6
+BE-16,8
diff --git a/Emby.Server.Implementations/Localization/Ratings/de.csv b/Emby.Server.Implementations/Localization/Ratings/de.csv
new file mode 100644
index 000000000..f944a140d
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/de.csv
@@ -0,0 +1,10 @@
+DE-0,1
+FSK-0,1
+DE-6,5
+FSK-6,5
+DE-12,7
+FSK-12,7
+DE-16,8
+FSK-16,8
+DE-18,9
+FSK-18,9
diff --git a/Emby.Server.Implementations/Localization/Ratings/kz.csv b/Emby.Server.Implementations/Localization/Ratings/kz.csv
index 4441c5650..d546bff53 100644
--- a/Emby.Server.Implementations/Localization/Ratings/kz.csv
+++ b/Emby.Server.Implementations/Localization/Ratings/kz.csv
@@ -1,6 +1,7 @@
-KZ-К,1
-KZ-БА,6
-KZ-Б14,7
-KZ-Е16,8
-KZ-Е18,10
-KZ-НА,15
+KZ-6-,0
+KZ-6+,6
+KZ-12+,12
+KZ-14+,14
+KZ-16+,16
+KZ-18+,18
+KZ-21+,21
diff --git a/Emby.Server.Implementations/Localization/Ratings/ru.csv b/Emby.Server.Implementations/Localization/Ratings/ru.csv
new file mode 100644
index 000000000..1bc94affd
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Ratings/ru.csv
@@ -0,0 +1,5 @@
+RU-0+,1
+RU-6+,3
+RU-12+,7
+RU-16+,9
+RU-18+,10
diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
index e68046f6d..52d07d784 100644
--- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -202,6 +202,10 @@ namespace Emby.Server.Implementations.MediaEncoder
private static List<string> GetSavedChapterImages(Video video, IDirectoryService directoryService)
{
var path = GetChapterImagesPath(video);
+ if (!Directory.Exists(path))
+ {
+ return new List<string>();
+ }
try
{
diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs
new file mode 100644
index 000000000..268bf4042
--- /dev/null
+++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs
@@ -0,0 +1,39 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using WebSocketManager = Emby.Server.Implementations.WebSockets.WebSocketManager;
+
+namespace Emby.Server.Implementations.Middleware
+{
+ public class WebSocketMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ILogger<WebSocketMiddleware> _logger;
+ private readonly WebSocketManager _webSocketManager;
+
+ public WebSocketMiddleware(RequestDelegate next, ILogger<WebSocketMiddleware> logger, WebSocketManager webSocketManager)
+ {
+ _next = next;
+ _logger = logger;
+ _webSocketManager = webSocketManager;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ _logger.LogInformation("Handling request: " + httpContext.Request.Path);
+
+ if (httpContext.WebSockets.IsWebSocketRequest)
+ {
+ var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
+ if (webSocketContext != null)
+ {
+ await _webSocketManager.OnWebSocketConnected(webSocketContext);
+ }
+ }
+ else
+ {
+ await _next.Invoke(httpContext);
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs
index 4671de07c..4d160aa66 100644
--- a/Emby.Server.Implementations/Net/IWebSocket.cs
+++ b/Emby.Server.Implementations/Net/IWebSocket.cs
@@ -45,9 +45,4 @@ namespace Emby.Server.Implementations.Net
/// <returns>Task.</returns>
Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken);
}
-
- public interface IMemoryWebSocket
- {
- Action<Memory<byte>, int> OnReceiveMemoryBytes { get; set; }
- }
}
diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
index 3ab8e854a..e3047d392 100644
--- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
+++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs
@@ -1,5 +1,7 @@
using System;
+using System.Net.WebSockets;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.Net
{
@@ -14,7 +16,7 @@ namespace Emby.Server.Implementations.Net
/// Gets or sets the query string.
/// </summary>
/// <value>The query string.</value>
- public QueryParamCollection QueryString { get; set; }
+ public IQueryCollection QueryString { get; set; }
/// <summary>
/// Gets or sets the web socket.
/// </summary>
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
index 60cc9b88e..ace93ebde 100644
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ b/Emby.Server.Implementations/Networking/NetworkManager.cs
@@ -79,13 +79,13 @@ namespace Emby.Server.Implementations.Networking
private IpAddressInfo[] _localIpAddresses;
private readonly object _localIpAddressSyncLock = new object();
- public IpAddressInfo[] GetLocalIpAddresses()
+ public IpAddressInfo[] GetLocalIpAddresses(bool ignoreVirtualInterface = true)
{
lock (_localIpAddressSyncLock)
{
if (_localIpAddresses == null)
{
- var addresses = GetLocalIpAddressesInternal().Result.Select(ToIpAddressInfo).ToArray();
+ var addresses = GetLocalIpAddressesInternal(ignoreVirtualInterface).Result.Select(ToIpAddressInfo).ToArray();
_localIpAddresses = addresses;
@@ -95,9 +95,9 @@ namespace Emby.Server.Implementations.Networking
}
}
- private async Task<List<IPAddress>> GetLocalIpAddressesInternal()
+ private async Task<List<IPAddress>> GetLocalIpAddressesInternal(bool ignoreVirtualInterface)
{
- var list = GetIPsDefault()
+ var list = GetIPsDefault(ignoreVirtualInterface)
.ToList();
if (list.Count == 0)
@@ -383,7 +383,7 @@ namespace Emby.Server.Implementations.Networking
return Dns.GetHostAddressesAsync(hostName);
}
- private List<IPAddress> GetIPsDefault()
+ private List<IPAddress> GetIPsDefault(bool ignoreVirtualInterface)
{
NetworkInterface[] interfaces;
@@ -414,7 +414,7 @@ namespace Emby.Server.Implementations.Networking
// Try to exclude virtual adapters
// http://stackoverflow.com/questions/8089685/c-sharp-finding-my-machines-local-ip-address-and-not-the-vms
var addr = ipProperties.GatewayAddresses.FirstOrDefault();
- if (addr == null || string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
+ if (addr == null || ignoreVirtualInterface && string.Equals(addr.Address.ToString(), "0.0.0.0", StringComparison.OrdinalIgnoreCase))
{
return new List<IPAddress>();
}
@@ -636,6 +636,66 @@ namespace Emby.Server.Implementations.Networking
return false;
}
+ public bool IsInSameSubnet(IpAddressInfo address1, IpAddressInfo address2, IpAddressInfo subnetMask)
+ {
+ IPAddress network1 = GetNetworkAddress(ToIPAddress(address1), ToIPAddress(subnetMask));
+ IPAddress network2 = GetNetworkAddress(ToIPAddress(address2), ToIPAddress(subnetMask));
+ return network1.Equals(network2);
+ }
+
+ private IPAddress GetNetworkAddress(IPAddress address, IPAddress subnetMask)
+ {
+ byte[] ipAdressBytes = address.GetAddressBytes();
+ byte[] subnetMaskBytes = subnetMask.GetAddressBytes();
+
+ if (ipAdressBytes.Length != subnetMaskBytes.Length)
+ {
+ throw new ArgumentException("Lengths of IP address and subnet mask do not match.");
+ }
+
+ byte[] broadcastAddress = new byte[ipAdressBytes.Length];
+ for (int i = 0; i < broadcastAddress.Length; i++)
+ {
+ broadcastAddress[i] = (byte)(ipAdressBytes[i] & (subnetMaskBytes[i]));
+ }
+ return new IPAddress(broadcastAddress);
+ }
+
+ public IpAddressInfo GetLocalIpSubnetMask(IpAddressInfo address)
+ {
+ NetworkInterface[] interfaces;
+ IPAddress ipaddress = ToIPAddress(address);
+
+ try
+ {
+ var validStatuses = new[] { OperationalStatus.Up, OperationalStatus.Unknown };
+
+ interfaces = NetworkInterface.GetAllNetworkInterfaces()
+ .Where(i => validStatuses.Contains(i.OperationalStatus))
+ .ToArray();
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Error in GetAllNetworkInterfaces");
+ return null;
+ }
+
+ foreach (NetworkInterface ni in interfaces)
+ {
+ if (ni.GetIPProperties().GatewayAddresses.FirstOrDefault() != null)
+ {
+ foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
+ {
+ if (ip.Address.Equals(ipaddress) && ip.IPv4Mask != null)
+ {
+ return ToIpAddressInfo(ip.IPv4Mask);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
public static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint)
{
if (endpoint == null)
diff --git a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
index 8a7c1492d..cad66a80f 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.Playlists
{
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var playlist = (Playlist)item;
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager;
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -89,7 +89,6 @@ namespace Emby.Server.Implementations.Playlists
Recursive = true,
ImageTypes = new[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
-
});
}
}
@@ -103,7 +102,7 @@ namespace Emby.Server.Implementations.Playlists
_libraryManager = libraryManager;
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -116,11 +115,5 @@ namespace Emby.Server.Implementations.Playlists
DtoOptions = new DtoOptions(false)
});
}
-
- //protected override Task<string> CreateImage(IHasMetadata item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
- //{
- // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
- //}
}
-
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
index 98685cebe..ec9466c4a 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
var dueTime = triggerDate - now;
- logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
+ logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime);
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
diff --git a/Emby.Server.Implementations/Security/EncryptionManager.cs b/Emby.Server.Implementations/Security/EncryptionManager.cs
deleted file mode 100644
index fa8872ccc..000000000
--- a/Emby.Server.Implementations/Security/EncryptionManager.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using System;
-using System.Text;
-using MediaBrowser.Controller.Security;
-
-namespace Emby.Server.Implementations.Security
-{
- public class EncryptionManager : IEncryptionManager
- {
- /// <summary>
- /// Encrypts the string.
- /// </summary>
- /// <param name="value">The value.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">value</exception>
- public string EncryptString(string value)
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- return EncryptStringUniversal(value);
- }
-
- /// <summary>
- /// Decrypts the string.
- /// </summary>
- /// <param name="value">The value.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="ArgumentNullException">value</exception>
- public string DecryptString(string value)
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- return DecryptStringUniversal(value);
- }
-
- private static string EncryptStringUniversal(string value)
- {
- // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now
-
- var bytes = Encoding.UTF8.GetBytes(value);
- return Convert.ToBase64String(bytes);
- }
-
- private static string DecryptStringUniversal(string value)
- {
- // Yes, this isn't good, but ProtectedData in mono is throwing exceptions, so use this for now
-
- var bytes = Convert.FromBase64String(value);
- return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
- }
- }
-}
diff --git a/Emby.Server.Implementations/Serialization/JsonSerializer.cs b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
index 44898d498..8ae7fd90c 100644
--- a/Emby.Server.Implementations/Serialization/JsonSerializer.cs
+++ b/Emby.Server.Implementations/Serialization/JsonSerializer.cs
@@ -42,6 +42,27 @@ namespace Emby.Server.Implementations.Serialization
}
/// <summary>
+ /// Serializes to stream.
+ /// </summary>
+ /// <param name="obj">The obj.</param>
+ /// <param name="stream">The stream.</param>
+ /// <exception cref="ArgumentNullException">obj</exception>
+ public void SerializeToStream<T>(T obj, Stream stream)
+ {
+ if (obj == null)
+ {
+ throw new ArgumentNullException(nameof(obj));
+ }
+
+ if (stream == null)
+ {
+ throw new ArgumentNullException(nameof(stream));
+ }
+
+ ServiceStack.Text.JsonSerializer.SerializeToStream<T>(obj, stream);
+ }
+
+ /// <summary>
/// Serializes to file.
/// </summary>
/// <param name="obj">The obj.</param>
diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs
index 296da2f7a..b6758486c 100644
--- a/Emby.Server.Implementations/Services/HttpResult.cs
+++ b/Emby.Server.Implementations/Services/HttpResult.cs
@@ -43,14 +43,9 @@ namespace Emby.Server.Implementations.Services
{
var contentLength = bytesResponse.Length;
- if (response != null)
- {
- response.SetContentLength(contentLength);
- }
-
if (contentLength > 0)
{
- await responseStream.WriteAsync(bytesResponse, 0, contentLength).ConfigureAwait(false);
+ await responseStream.WriteAsync(bytesResponse, 0, contentLength, cancellationToken).ConfigureAwait(false);
}
return;
}
diff --git a/Emby.Server.Implementations/Services/ResponseHelper.cs b/Emby.Server.Implementations/Services/ResponseHelper.cs
index dc9975347..0301ff335 100644
--- a/Emby.Server.Implementations/Services/ResponseHelper.cs
+++ b/Emby.Server.Implementations/Services/ResponseHelper.cs
@@ -20,8 +20,6 @@ namespace Emby.Server.Implementations.Services
{
response.StatusCode = (int)HttpStatusCode.NoContent;
}
-
- response.SetContentLength(0);
return Task.CompletedTask;
}
@@ -55,7 +53,6 @@ namespace Emby.Server.Implementations.Services
{
if (string.Equals(responseHeaders.Key, "Content-Length", StringComparison.OrdinalIgnoreCase))
{
- response.SetContentLength(long.Parse(responseHeaders.Value));
continue;
}
@@ -104,7 +101,6 @@ namespace Emby.Server.Implementations.Services
if (bytes != null)
{
response.ContentType = "application/octet-stream";
- response.SetContentLength(bytes.Length);
if (bytes.Length > 0)
{
@@ -117,7 +113,6 @@ namespace Emby.Server.Implementations.Services
if (responseText != null)
{
bytes = Encoding.UTF8.GetBytes(responseText);
- response.SetContentLength(bytes.Length);
if (bytes.Length > 0)
{
return response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
@@ -149,8 +144,6 @@ namespace Emby.Server.Implementations.Services
var contentLength = ms.Length;
- response.SetContentLength(contentLength);
-
if (contentLength > 0)
{
await ms.CopyToAsync(response.OutputStream).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs
index 79f5c59e6..38952628d 100644
--- a/Emby.Server.Implementations/Services/ServiceExec.cs
+++ b/Emby.Server.Implementations/Services/ServiceExec.cs
@@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Services
foreach (var requestFilter in actionContext.RequestFilters)
{
requestFilter.RequestFilter(request, request.Response, requestDto);
- if (request.Response.IsClosed)
+ if (request.Response.OriginalResponse.HasStarted)
{
Task.FromResult<object>(null);
}
diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs
index 7e836e22c..3c8adfc98 100644
--- a/Emby.Server.Implementations/Services/ServiceHandler.cs
+++ b/Emby.Server.Implementations/Services/ServiceHandler.cs
@@ -154,7 +154,7 @@ namespace Emby.Server.Implementations.Services
{
if (name == null) continue; //thank you ASP.NET
- var values = request.QueryString.GetValues(name);
+ var values = request.QueryString[name];
if (values.Count == 1)
{
map[name] = values[0];
diff --git a/Emby.Server.Implementations/Services/ServicePath.cs b/Emby.Server.Implementations/Services/ServicePath.cs
index f575baca3..ccb28e8df 100644
--- a/Emby.Server.Implementations/Services/ServicePath.cs
+++ b/Emby.Server.Implementations/Services/ServicePath.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Services
private const char ComponentSeperator = '.';
private const string VariablePrefix = "{";
- readonly bool[] componentsWithSeparators;
+ private readonly bool[] componentsWithSeparators;
private readonly string restPath;
public bool IsWildCardPath { get; private set; }
@@ -54,10 +54,6 @@ namespace Emby.Server.Implementations.Services
public string Description { get; private set; }
public bool IsHidden { get; private set; }
- public int Priority { get; set; } //passed back to RouteAttribute
-
- public IEnumerable<string> PathVariables => this.variablesNames.Where(e => !string.IsNullOrWhiteSpace(e));
-
public static string[] GetPathPartsForMatching(string pathInfo)
{
return pathInfo.ToLowerInvariant().Split(new[] { PathSeperatorChar }, StringSplitOptions.RemoveEmptyEntries);
@@ -83,9 +79,12 @@ namespace Emby.Server.Implementations.Services
{
list.Add(hashPrefix + part);
- var subParts = part.Split(ComponentSeperator);
- if (subParts.Length == 1) continue;
+ if (part.IndexOf(ComponentSeperator) == -1)
+ {
+ continue;
+ }
+ var subParts = part.Split(ComponentSeperator);
foreach (var subPart in subParts)
{
list.Add(hashPrefix + subPart);
@@ -114,7 +113,7 @@ namespace Emby.Server.Implementations.Services
{
if (string.IsNullOrEmpty(component)) continue;
- if (StringContains(component, VariablePrefix)
+ if (component.IndexOf(VariablePrefix, StringComparison.OrdinalIgnoreCase) != -1
&& component.IndexOf(ComponentSeperator) != -1)
{
hasSeparators.Add(true);
@@ -165,7 +164,11 @@ namespace Emby.Server.Implementations.Services
for (var i = 0; i < components.Length - 1; i++)
{
- if (!this.isWildcard[i]) continue;
+ if (!this.isWildcard[i])
+ {
+ continue;
+ }
+
if (this.literalsToMatch[i + 1] == null)
{
throw new ArgumentException(
@@ -173,7 +176,7 @@ namespace Emby.Server.Implementations.Services
}
}
- this.wildcardCount = this.isWildcard.Count(x => x);
+ this.wildcardCount = this.isWildcard.Length;
this.IsWildCardPath = this.wildcardCount > 0;
this.FirstMatchHashKey = !this.IsWildCardPath
@@ -181,19 +184,14 @@ namespace Emby.Server.Implementations.Services
: WildCardChar + PathSeperator + firstLiteralMatch;
this.typeDeserializer = new StringMapTypeDeserializer(createInstanceFn, getParseFn, this.RequestType);
- RegisterCaseInsenstivePropertyNameMappings();
- }
- private void RegisterCaseInsenstivePropertyNameMappings()
- {
- foreach (var propertyInfo in GetSerializableProperties(RequestType))
- {
- var propertyName = propertyInfo.Name;
- propertyNamesMap.Add(propertyName.ToLowerInvariant(), propertyName);
- }
+ _propertyNamesMap = new HashSet<string>(
+ GetSerializableProperties(RequestType).Select(x => x.Name),
+ StringComparer.OrdinalIgnoreCase);
}
- internal static string[] IgnoreAttributesNamed = new[] {
+ internal static string[] IgnoreAttributesNamed = new[]
+ {
"IgnoreDataMemberAttribute",
"JsonIgnoreAttribute"
};
@@ -201,19 +199,12 @@ namespace Emby.Server.Implementations.Services
private static Type excludeType = typeof(Stream);
- internal static List<PropertyInfo> GetSerializableProperties(Type type)
+ internal static IEnumerable<PropertyInfo> GetSerializableProperties(Type type)
{
- var list = new List<PropertyInfo>();
- var props = GetPublicProperties(type);
-
- foreach (var prop in props)
+ foreach (var prop in GetPublicProperties(type))
{
- if (prop.GetMethod == null)
- {
- continue;
- }
-
- if (excludeType == prop.PropertyType)
+ if (prop.GetMethod == null
+ || excludeType == prop.PropertyType)
{
continue;
}
@@ -230,23 +221,21 @@ namespace Emby.Server.Implementations.Services
if (!ignored)
{
- list.Add(prop);
+ yield return prop;
}
}
-
- // else return those properties that are not decorated with IgnoreDataMember
- return list;
}
- private static List<PropertyInfo> GetPublicProperties(Type type)
+ private static IEnumerable<PropertyInfo> GetPublicProperties(Type type)
{
- if (type.GetTypeInfo().IsInterface)
+ if (type.IsInterface)
{
var propertyInfos = new List<PropertyInfo>();
-
- var considered = new List<Type>();
+ var considered = new List<Type>()
+ {
+ type
+ };
var queue = new Queue<Type>();
- considered.Add(type);
queue.Enqueue(type);
while (queue.Count > 0)
@@ -254,15 +243,16 @@ namespace Emby.Server.Implementations.Services
var subType = queue.Dequeue();
foreach (var subInterface in subType.GetTypeInfo().ImplementedInterfaces)
{
- if (considered.Contains(subInterface)) continue;
+ if (considered.Contains(subInterface))
+ {
+ continue;
+ }
considered.Add(subInterface);
queue.Enqueue(subInterface);
}
- var typeProperties = GetTypesPublicProperties(subType);
-
- var newPropertyInfos = typeProperties
+ var newPropertyInfos = GetTypesPublicProperties(subType)
.Where(x => !propertyInfos.Contains(x));
propertyInfos.InsertRange(0, newPropertyInfos);
@@ -271,28 +261,22 @@ namespace Emby.Server.Implementations.Services
return propertyInfos;
}
- var list = new List<PropertyInfo>();
-
- foreach (var t in GetTypesPublicProperties(type))
- {
- if (t.GetIndexParameters().Length == 0)
- {
- list.Add(t);
- }
- }
- return list;
+ return GetTypesPublicProperties(type)
+ .Where(x => x.GetIndexParameters().Length == 0);
}
- private static PropertyInfo[] GetTypesPublicProperties(Type subType)
+ private static IEnumerable<PropertyInfo> GetTypesPublicProperties(Type subType)
{
- var pis = new List<PropertyInfo>();
foreach (var pi in subType.GetRuntimeProperties())
{
var mi = pi.GetMethod ?? pi.SetMethod;
- if (mi != null && mi.IsStatic) continue;
- pis.Add(pi);
+ if (mi != null && mi.IsStatic)
+ {
+ continue;
+ }
+
+ yield return pi;
}
- return pis.ToArray();
}
/// <summary>
@@ -302,7 +286,7 @@ namespace Emby.Server.Implementations.Services
private readonly StringMapTypeDeserializer typeDeserializer;
- private readonly Dictionary<string, string> propertyNamesMap = new Dictionary<string, string>();
+ private readonly HashSet<string> _propertyNamesMap;
public int MatchScore(string httpMethod, string[] withPathInfoParts)
{
@@ -312,13 +296,10 @@ namespace Emby.Server.Implementations.Services
return -1;
}
- var score = 0;
-
//Routes with least wildcard matches get the highest score
- score += Math.Max((100 - wildcardMatchCount), 1) * 1000;
-
- //Routes with less variable (and more literal) matches
- score += Math.Max((10 - VariableArgsCount), 1) * 100;
+ var score = Math.Max((100 - wildcardMatchCount), 1) * 1000
+ //Routes with less variable (and more literal) matches
+ + Math.Max((10 - VariableArgsCount), 1) * 100;
//Exact verb match is better than ANY
if (Verbs.Length == 1 && string.Equals(httpMethod, Verbs[0], StringComparison.OrdinalIgnoreCase))
@@ -333,11 +314,6 @@ namespace Emby.Server.Implementations.Services
return score;
}
- private bool StringContains(string str1, string str2)
- {
- return str1.IndexOf(str2, StringComparison.OrdinalIgnoreCase) != -1;
- }
-
/// <summary>
/// For performance withPathInfoParts should already be a lower case string
/// to minimize redundant matching operations.
@@ -374,7 +350,8 @@ namespace Emby.Server.Implementations.Services
if (i < this.TotalComponentsCount - 1)
{
// Continue to consume up until a match with the next literal
- while (pathIx < withPathInfoParts.Length && !LiteralsEqual(withPathInfoParts[pathIx], this.literalsToMatch[i + 1]))
+ while (pathIx < withPathInfoParts.Length
+ && !string.Equals(withPathInfoParts[pathIx], this.literalsToMatch[i + 1], StringComparison.InvariantCultureIgnoreCase))
{
pathIx++;
wildcardMatchCount++;
@@ -403,10 +380,12 @@ namespace Emby.Server.Implementations.Services
continue;
}
- if (withPathInfoParts.Length <= pathIx || !LiteralsEqual(withPathInfoParts[pathIx], literalToMatch))
+ if (withPathInfoParts.Length <= pathIx
+ || !string.Equals(withPathInfoParts[pathIx], literalToMatch, StringComparison.InvariantCultureIgnoreCase))
{
return false;
}
+
pathIx++;
}
}
@@ -414,35 +393,26 @@ namespace Emby.Server.Implementations.Services
return pathIx == withPathInfoParts.Length;
}
- private static bool LiteralsEqual(string str1, string str2)
- {
- // Most cases
- if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- // Handle turkish i
- str1 = str1.ToUpperInvariant();
- str2 = str2.ToUpperInvariant();
-
- // Invariant IgnoreCase would probably be better but it's not available in PCL
- return string.Equals(str1, str2, StringComparison.CurrentCultureIgnoreCase);
- }
-
private bool ExplodeComponents(ref string[] withPathInfoParts)
{
var totalComponents = new List<string>();
for (var i = 0; i < withPathInfoParts.Length; i++)
{
var component = withPathInfoParts[i];
- if (string.IsNullOrEmpty(component)) continue;
+ if (string.IsNullOrEmpty(component))
+ {
+ continue;
+ }
if (this.PathComponentsCount != this.TotalComponentsCount
&& this.componentsWithSeparators[i])
{
var subComponents = component.Split(ComponentSeperator);
- if (subComponents.Length < 2) return false;
+ if (subComponents.Length < 2)
+ {
+ return false;
+ }
+
totalComponents.AddRange(subComponents);
}
else
@@ -483,7 +453,7 @@ namespace Emby.Server.Implementations.Services
continue;
}
- if (!this.propertyNamesMap.TryGetValue(variableName.ToLowerInvariant(), out var propertyNameOnRequest))
+ if (!this._propertyNamesMap.Contains(variableName))
{
if (string.Equals("ignore", variableName, StringComparison.OrdinalIgnoreCase))
{
@@ -507,6 +477,7 @@ namespace Emby.Server.Implementations.Services
{
sb.Append(PathSeperatorChar + requestComponents[j]);
}
+
value = sb.ToString();
}
else
@@ -517,13 +488,13 @@ namespace Emby.Server.Implementations.Services
var stopLiteral = i == this.TotalComponentsCount - 1 ? null : this.literalsToMatch[i + 1];
if (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
- var sb = new StringBuilder();
- sb.Append(value);
+ var sb = new StringBuilder(value);
pathIx++;
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
{
sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
}
+
value = sb.ToString();
}
else
@@ -538,7 +509,7 @@ namespace Emby.Server.Implementations.Services
pathIx++;
}
- requestKeyValuesMap[propertyNameOnRequest] = value;
+ requestKeyValuesMap[variableName] = value;
}
if (queryStringAndFormData != null)
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index d13935fba..f835aa1b5 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -11,15 +11,16 @@ namespace Emby.Server.Implementations.Services
{
internal class PropertySerializerEntry
{
- public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn)
+ public PropertySerializerEntry(Action<object, object> propertySetFn, Func<string, object> propertyParseStringFn, Type propertyType)
{
PropertySetFn = propertySetFn;
PropertyParseStringFn = propertyParseStringFn;
+ PropertyType = PropertyType;
}
- public Action<object, object> PropertySetFn;
- public Func<string, object> PropertyParseStringFn;
- public Type PropertyType;
+ public Action<object, object> PropertySetFn { get; private set; }
+ public Func<string, object> PropertyParseStringFn { get; private set; }
+ public Type PropertyType { get; private set; }
}
private readonly Type type;
@@ -29,7 +30,9 @@ namespace Emby.Server.Implementations.Services
public Func<string, object> GetParseFn(Type propertyType)
{
if (propertyType == typeof(string))
+ {
return s => s;
+ }
return _GetParseFn(propertyType);
}
@@ -48,7 +51,7 @@ namespace Emby.Server.Implementations.Services
var propertySetFn = TypeAccessor.GetSetPropertyMethod(type, propertyInfo);
var propertyType = propertyInfo.PropertyType;
var propertyParseStringFn = GetParseFn(propertyType);
- var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn) { PropertyType = propertyType };
+ var propertySerializer = new PropertySerializerEntry(propertySetFn, propertyParseStringFn, propertyType);
propertySetterMap[propertyInfo.Name] = propertySerializer;
}
@@ -56,34 +59,21 @@ namespace Emby.Server.Implementations.Services
public object PopulateFromMap(object instance, IDictionary<string, string> keyValuePairs)
{
- string propertyName = null;
- string propertyTextValue = null;
PropertySerializerEntry propertySerializerEntry = null;
if (instance == null)
+ {
instance = _CreateInstanceFn(type);
+ }
foreach (var pair in keyValuePairs)
{
- propertyName = pair.Key;
- propertyTextValue = pair.Value;
-
- if (string.IsNullOrEmpty(propertyTextValue))
- {
- continue;
- }
+ string propertyName = pair.Key;
+ string propertyTextValue = pair.Value;
- if (!propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry))
- {
- if (propertyName == "v")
- {
- continue;
- }
-
- continue;
- }
-
- if (propertySerializerEntry.PropertySetFn == null)
+ if (string.IsNullOrEmpty(propertyTextValue)
+ || !propertySetterMap.TryGetValue(propertyName, out propertySerializerEntry)
+ || propertySerializerEntry.PropertySetFn == null)
{
continue;
}
@@ -99,6 +89,7 @@ namespace Emby.Server.Implementations.Services
{
continue;
}
+
propertySerializerEntry.PropertySetFn(instance, value);
}
@@ -107,7 +98,11 @@ namespace Emby.Server.Implementations.Services
public static string LeftPart(string strVal, char needle)
{
- if (strVal == null) return null;
+ if (strVal == null)
+ {
+ return null;
+ }
+
var pos = strVal.IndexOf(needle);
return pos == -1
? strVal
@@ -119,7 +114,10 @@ namespace Emby.Server.Implementations.Services
{
public static Action<object, object> GetSetPropertyMethod(Type type, PropertyInfo propertyInfo)
{
- if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0) return null;
+ if (!propertyInfo.CanWrite || propertyInfo.GetIndexParameters().Length > 0)
+ {
+ return null;
+ }
var setMethodInfo = propertyInfo.SetMethod;
return (instance, value) => setMethodInfo.Invoke(instance, new[] { value });
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index fa0ab62d3..03e7b2654 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -1090,7 +1090,7 @@ namespace Emby.Server.Implementations.Session
await SendMessageToSession(session, "Play", command, cancellationToken).ConfigureAwait(false);
}
- private IList<BaseItem> TranslateItemForPlayback(Guid id, User user)
+ private IEnumerable<BaseItem> TranslateItemForPlayback(Guid id, User user)
{
var item = _libraryManager.GetItemById(id);
diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
index 24903f5e8..a551433ed 100644
--- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
+++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Session
@@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Session
}
}
- private SessionInfo GetSession(QueryParamCollection queryString, string remoteEndpoint)
+ private SessionInfo GetSession(IQueryCollection queryString, string remoteEndpoint)
{
if (queryString == null)
{
diff --git a/Emby.Server.Implementations/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs
new file mode 100644
index 000000000..120ac50d9
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/HttpFile.cs
@@ -0,0 +1,18 @@
+using System.IO;
+using MediaBrowser.Model.Services;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public class HttpFile : IHttpFile
+ {
+ public string Name { get; set; }
+
+ public string FileName { get; set; }
+
+ public long ContentLength { get; set; }
+
+ public string ContentType { get; set; }
+
+ public Stream InputStream { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs
new file mode 100644
index 000000000..f38ed848e
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs
@@ -0,0 +1,204 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Services;
+
+public sealed class HttpPostedFile : IDisposable
+{
+ private string _name;
+ private string _contentType;
+ private Stream _stream;
+ private bool _disposed = false;
+
+ internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
+ {
+ _name = name;
+ _contentType = content_type;
+ _stream = new ReadSubStream(base_stream, offset, length);
+ }
+
+ public string ContentType => _contentType;
+
+ public int ContentLength => (int)_stream.Length;
+
+ public string FileName => _name;
+
+ public Stream InputStream => _stream;
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ public void Dispose()
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _stream.Dispose();
+ _stream = null;
+
+ _name = null;
+ _contentType = null;
+
+ _disposed = true;
+ }
+
+ private class ReadSubStream : Stream
+ {
+ private Stream _stream;
+ private long _offset;
+ private long _end;
+ private long _position;
+
+ public ReadSubStream(Stream s, long offset, long length)
+ {
+ _stream = s;
+ _offset = offset;
+ _end = offset + length;
+ _position = offset;
+ }
+
+ public override void Flush()
+ {
+ }
+
+ public override int Read(byte[] buffer, int dest_offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (dest_offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dest_offset), "< 0");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), "< 0");
+ }
+
+ int len = buffer.Length;
+ if (dest_offset > len)
+ {
+ throw new ArgumentException("destination offset is beyond array size", nameof(dest_offset));
+ }
+
+ // reordered to avoid possible integer overflow
+ if (dest_offset > len - count)
+ {
+ throw new ArgumentException("Reading would overrun buffer", nameof(count));
+ }
+
+ if (count > _end - _position)
+ {
+ count = (int)(_end - _position);
+ }
+
+ if (count <= 0)
+ {
+ return 0;
+ }
+
+ _stream.Position = _position;
+ int result = _stream.Read(buffer, dest_offset, count);
+ if (result > 0)
+ {
+ _position += result;
+ }
+ else
+ {
+ _position = _end;
+ }
+
+ return result;
+ }
+
+ public override int ReadByte()
+ {
+ if (_position >= _end)
+ {
+ return -1;
+ }
+
+ _stream.Position = _position;
+ int result = _stream.ReadByte();
+ if (result < 0)
+ {
+ _position = _end;
+ }
+ else
+ {
+ _position++;
+ }
+
+ return result;
+ }
+
+ public override long Seek(long d, SeekOrigin origin)
+ {
+ long real;
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ real = _offset + d;
+ break;
+ case SeekOrigin.End:
+ real = _end + d;
+ break;
+ case SeekOrigin.Current:
+ real = _position + d;
+ break;
+ default:
+ throw new ArgumentException("Unknown SeekOrigin value", nameof(origin));
+ }
+
+ long virt = real - _offset;
+ if (virt < 0 || virt > Length)
+ {
+ throw new ArgumentException("Invalid position", nameof(d));
+ }
+
+ _position = _stream.Seek(real, SeekOrigin.Begin);
+ return _position;
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override bool CanRead => true;
+
+ public override bool CanSeek => true;
+
+ public override bool CanWrite => false;
+
+ public override long Length => _end - _offset;
+
+ public override long Position
+ {
+ get => _position - _offset;
+ set
+ {
+ if (value > Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ _position = Seek(value, SeekOrigin.Begin);
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs
new file mode 100644
index 000000000..373f6d758
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs
@@ -0,0 +1,659 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Services;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public partial class WebSocketSharpRequest : IHttpRequest
+ {
+ internal static string GetParameter(ReadOnlySpan<char> header, string attr)
+ {
+ int ap = header.IndexOf(attr.AsSpan(), StringComparison.Ordinal);
+ if (ap == -1)
+ {
+ return null;
+ }
+
+ ap += attr.Length;
+ if (ap >= header.Length)
+ {
+ return null;
+ }
+
+ char ending = header[ap];
+ if (ending != '"')
+ {
+ ending = ' ';
+ }
+
+ var slice = header.Slice(ap + 1);
+ int end = slice.IndexOf(ending);
+ if (end == -1)
+ {
+ return ending == '"' ? null : header.Slice(ap).ToString();
+ }
+
+ return slice.Slice(0, end - ap - 1).ToString();
+ }
+
+ private async Task LoadMultiPart(WebROCollection form)
+ {
+ string boundary = GetParameter(ContentType.AsSpan(), "; boundary=");
+ if (boundary == null)
+ {
+ return;
+ }
+
+ using (var requestStream = InputStream)
+ {
+ // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
+ // Not ending with \r\n?
+ var ms = new MemoryStream(32 * 1024);
+ await requestStream.CopyToAsync(ms).ConfigureAwait(false);
+
+ var input = ms;
+ ms.WriteByte((byte)'\r');
+ ms.WriteByte((byte)'\n');
+
+ input.Position = 0;
+
+ // Uncomment to debug
+ // var content = new StreamReader(ms).ReadToEnd();
+ // Console.WriteLine(boundary + "::" + content);
+ // input.Position = 0;
+
+ var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
+
+ HttpMultipart.Element e;
+ while ((e = multi_part.ReadNextElement()) != null)
+ {
+ if (e.Filename == null)
+ {
+ byte[] copy = new byte[e.Length];
+
+ input.Position = e.Start;
+ await input.ReadAsync(copy, 0, (int)e.Length).ConfigureAwait(false);
+
+ form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length));
+ }
+ else
+ {
+ // We use a substream, as in 2.x we will support large uploads streamed to disk,
+ var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
+ files[e.Name] = sub;
+ }
+ }
+ }
+ }
+
+ public async Task<QueryParamCollection> GetFormData()
+ {
+ var form = new WebROCollection();
+ files = new Dictionary<string, HttpPostedFile>();
+
+ if (IsContentType("multipart/form-data"))
+ {
+ await LoadMultiPart(form).ConfigureAwait(false);
+ }
+ else if (IsContentType("application/x-www-form-urlencoded"))
+ {
+ await LoadWwwForm(form).ConfigureAwait(false);
+ }
+
+ if (validate_form && !checked_form)
+ {
+ checked_form = true;
+ ValidateNameValueCollection("Form", form);
+ }
+
+ return form;
+ }
+
+ public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString();
+
+ public string Authorization => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Authorization]) ? null : request.Headers[HeaderNames.Authorization].ToString();
+
+ protected bool validate_form { get; set; }
+ protected bool checked_form { get; set; }
+
+ private static void ThrowValidationException(string name, string key, string value)
+ {
+ string v = "\"" + value + "\"";
+ if (v.Length > 20)
+ {
+ v = v.Substring(0, 16) + "...\"";
+ }
+
+ string msg = string.Format(
+ CultureInfo.InvariantCulture,
+ "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
+ name,
+ key,
+ v);
+
+ throw new Exception(msg);
+ }
+
+ private static void ValidateNameValueCollection(string name, QueryParamCollection coll)
+ {
+ if (coll == null)
+ {
+ return;
+ }
+
+ foreach (var pair in coll)
+ {
+ var key = pair.Name;
+ var val = pair.Value;
+ if (val != null && val.Length > 0 && IsInvalidString(val))
+ {
+ ThrowValidationException(name, key, val);
+ }
+ }
+ }
+
+ internal static bool IsInvalidString(string val)
+ => IsInvalidString(val, out var validationFailureIndex);
+
+ internal static bool IsInvalidString(string val, out int validationFailureIndex)
+ {
+ validationFailureIndex = 0;
+
+ int len = val.Length;
+ if (len < 2)
+ {
+ return false;
+ }
+
+ char current = val[0];
+ for (int idx = 1; idx < len; idx++)
+ {
+ char next = val[idx];
+
+ // See http://secunia.com/advisories/14325
+ if (current == '<' || current == '\xff1c')
+ {
+ if (next == '!' || next < ' '
+ || (next >= 'a' && next <= 'z')
+ || (next >= 'A' && next <= 'Z'))
+ {
+ validationFailureIndex = idx - 1;
+ return true;
+ }
+ }
+ else if (current == '&' && next == '#')
+ {
+ validationFailureIndex = idx - 1;
+ return true;
+ }
+
+ current = next;
+ }
+
+ return false;
+ }
+
+ private bool IsContentType(string ct)
+ {
+ if (ContentType == null)
+ {
+ return false;
+ }
+
+ return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private async Task LoadWwwForm(WebROCollection form)
+ {
+ using (var input = InputStream)
+ {
+ using (var ms = new MemoryStream())
+ {
+ await input.CopyToAsync(ms).ConfigureAwait(false);
+ ms.Position = 0;
+
+ using (var s = new StreamReader(ms, ContentEncoding))
+ {
+ var key = new StringBuilder();
+ var value = new StringBuilder();
+ int c;
+
+ while ((c = s.Read()) != -1)
+ {
+ if (c == '=')
+ {
+ value.Length = 0;
+ while ((c = s.Read()) != -1)
+ {
+ if (c == '&')
+ {
+ AddRawKeyValue(form, key, value);
+ break;
+ }
+ else
+ {
+ value.Append((char)c);
+ }
+ }
+
+ if (c == -1)
+ {
+ AddRawKeyValue(form, key, value);
+ return;
+ }
+ }
+ else if (c == '&')
+ {
+ AddRawKeyValue(form, key, value);
+ }
+ else
+ {
+ key.Append((char)c);
+ }
+ }
+
+ if (c == -1)
+ {
+ AddRawKeyValue(form, key, value);
+ }
+ }
+ }
+ }
+ }
+
+ private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
+ {
+ form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString()));
+
+ key.Length = 0;
+ value.Length = 0;
+ }
+
+ private Dictionary<string, HttpPostedFile> files;
+
+ private class WebROCollection : QueryParamCollection
+ {
+ public override string ToString()
+ {
+ var result = new StringBuilder();
+ foreach (var pair in this)
+ {
+ if (result.Length > 0)
+ {
+ result.Append('&');
+ }
+
+ var key = pair.Name;
+ if (key != null && key.Length > 0)
+ {
+ result.Append(key);
+ result.Append('=');
+ }
+
+ result.Append(pair.Value);
+ }
+
+ return result.ToString();
+ }
+ }
+ private class HttpMultipart
+ {
+
+ public class Element
+ {
+ public string ContentType { get; set; }
+
+ public string Name { get; set; }
+
+ public string Filename { get; set; }
+
+ public Encoding Encoding { get; set; }
+
+ public long Start { get; set; }
+
+ public long Length { get; set; }
+
+ public override string ToString()
+ {
+ return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
+ Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture);
+ }
+ }
+
+ private const byte LF = (byte)'\n';
+
+ private const byte CR = (byte)'\r';
+
+ private Stream data;
+
+ private string boundary;
+
+ private byte[] boundaryBytes;
+
+ private byte[] buffer;
+
+ private bool atEof;
+
+ private Encoding encoding;
+
+ private StringBuilder sb;
+
+ // See RFC 2046
+ // In the case of multipart entities, in which one or more different
+ // sets of data are combined in a single body, a "multipart" media type
+ // field must appear in the entity's header. The body must then contain
+ // one or more body parts, each preceded by a boundary delimiter line,
+ // and the last one followed by a closing boundary delimiter line.
+ // After its boundary delimiter line, each body part then consists of a
+ // header area, a blank line, and a body area. Thus a body part is
+ // similar to an RFC 822 message in syntax, but different in meaning.
+
+ public HttpMultipart(Stream data, string b, Encoding encoding)
+ {
+ this.data = data;
+ boundary = b;
+ boundaryBytes = encoding.GetBytes(b);
+ buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
+ this.encoding = encoding;
+ sb = new StringBuilder();
+ }
+
+ public Element ReadNextElement()
+ {
+ if (atEof || ReadBoundary())
+ {
+ return null;
+ }
+
+ var elem = new Element();
+ ReadOnlySpan<char> header;
+ while ((header = ReadHeaders().AsSpan()) != null)
+ {
+ if (header.StartsWith("Content-Disposition:".AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ elem.Name = GetContentDispositionAttribute(header, "name");
+ elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
+ }
+ else if (header.StartsWith("Content-Type:".AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ elem.ContentType = header.Slice("Content-Type:".Length).Trim().ToString();
+ elem.Encoding = GetEncoding(elem.ContentType);
+ }
+ }
+
+ long start = data.Position;
+ elem.Start = start;
+ long pos = MoveToNextBoundary();
+ if (pos == -1)
+ {
+ return null;
+ }
+
+ elem.Length = pos - start;
+ return elem;
+ }
+
+ private string ReadLine()
+ {
+ // CRLF or LF are ok as line endings.
+ bool got_cr = false;
+ int b = 0;
+ sb.Length = 0;
+ while (true)
+ {
+ b = data.ReadByte();
+ if (b == -1)
+ {
+ return null;
+ }
+
+ if (b == LF)
+ {
+ break;
+ }
+
+ got_cr = b == CR;
+ sb.Append((char)b);
+ }
+
+ if (got_cr)
+ {
+ sb.Length--;
+ }
+
+ return sb.ToString();
+ }
+
+ private static string GetContentDispositionAttribute(ReadOnlySpan<char> l, string name)
+ {
+ int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal);
+ if (idx < 0)
+ {
+ return null;
+ }
+
+ int begin = idx + name.Length + "=\"".Length;
+ int end = l.Slice(begin).IndexOf('"');
+ if (end < 0)
+ {
+ return null;
+ }
+
+ if (begin == end)
+ {
+ return string.Empty;
+ }
+
+ return l.Slice(begin, end - begin).ToString();
+ }
+
+ private string GetContentDispositionAttributeWithEncoding(ReadOnlySpan<char> l, string name)
+ {
+ int idx = l.IndexOf((name + "=\"").AsSpan(), StringComparison.Ordinal);
+ if (idx < 0)
+ {
+ return null;
+ }
+
+ int begin = idx + name.Length + "=\"".Length;
+ int end = l.Slice(begin).IndexOf('"');
+ if (end < 0)
+ {
+ return null;
+ }
+
+ if (begin == end)
+ {
+ return string.Empty;
+ }
+
+ ReadOnlySpan<char> temp = l.Slice(begin, end - begin);
+ byte[] source = new byte[temp.Length];
+ for (int i = temp.Length - 1; i >= 0; i--)
+ {
+ source[i] = (byte)temp[i];
+ }
+
+ return encoding.GetString(source, 0, source.Length);
+ }
+
+ private bool ReadBoundary()
+ {
+ try
+ {
+ string line;
+ do
+ {
+ line = ReadLine();
+ }
+ while (line.Length == 0);
+
+ if (line[0] != '-' || line[1] != '-')
+ {
+ return false;
+ }
+
+ if (!line.EndsWith(boundary, StringComparison.Ordinal))
+ {
+ return true;
+ }
+ }
+ catch
+ {
+
+ }
+
+ return false;
+ }
+
+ private string ReadHeaders()
+ {
+ string s = ReadLine();
+ if (s.Length == 0)
+ {
+ return null;
+ }
+
+ return s;
+ }
+
+ private static bool CompareBytes(byte[] orig, byte[] other)
+ {
+ for (int i = orig.Length - 1; i >= 0; i--)
+ {
+ if (orig[i] != other[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private long MoveToNextBoundary()
+ {
+ long retval = 0;
+ bool got_cr = false;
+
+ int state = 0;
+ int c = data.ReadByte();
+ while (true)
+ {
+ if (c == -1)
+ {
+ return -1;
+ }
+
+ if (state == 0 && c == LF)
+ {
+ retval = data.Position - 1;
+ if (got_cr)
+ {
+ retval--;
+ }
+
+ state = 1;
+ c = data.ReadByte();
+ }
+ else if (state == 0)
+ {
+ got_cr = c == CR;
+ c = data.ReadByte();
+ }
+ else if (state == 1 && c == '-')
+ {
+ c = data.ReadByte();
+ if (c == -1)
+ {
+ return -1;
+ }
+
+ if (c != '-')
+ {
+ state = 0;
+ got_cr = false;
+ continue; // no ReadByte() here
+ }
+
+ int nread = data.Read(buffer, 0, buffer.Length);
+ int bl = buffer.Length;
+ if (nread != bl)
+ {
+ return -1;
+ }
+
+ if (!CompareBytes(boundaryBytes, buffer))
+ {
+ state = 0;
+ data.Position = retval + 2;
+ if (got_cr)
+ {
+ data.Position++;
+ got_cr = false;
+ }
+
+ c = data.ReadByte();
+ continue;
+ }
+
+ if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
+ {
+ atEof = true;
+ }
+ else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
+ {
+ state = 0;
+ data.Position = retval + 2;
+ if (got_cr)
+ {
+ data.Position++;
+ got_cr = false;
+ }
+
+ c = data.ReadByte();
+ continue;
+ }
+
+ data.Position = retval + 2;
+ if (got_cr)
+ {
+ data.Position++;
+ }
+
+ break;
+ }
+ else
+ {
+ // state == 1
+ state = 0; // no ReadByte() here
+ }
+ }
+
+ return retval;
+ }
+
+ private static string StripPath(string path)
+ {
+ if (path == null || path.Length == 0)
+ {
+ return path;
+ }
+
+ if (path.IndexOf(":\\", StringComparison.Ordinal) != 1
+ && !path.StartsWith("\\\\", StringComparison.Ordinal))
+ {
+ return path;
+ }
+
+ return path.Substring(path.LastIndexOf('\\') + 1);
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs
new file mode 100644
index 000000000..62b16ed8c
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Server.Implementations.Net;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public class SharpWebSocket : IWebSocket
+ {
+ /// <summary>
+ /// The logger
+ /// </summary>
+ private readonly ILogger _logger;
+
+ public event EventHandler<EventArgs> Closed;
+
+ /// <summary>
+ /// Gets or sets the web socket.
+ /// </summary>
+ /// <value>The web socket.</value>
+ private readonly WebSocket _webSocket;
+
+ private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+ private bool _disposed;
+
+ public SharpWebSocket(WebSocket socket, ILogger logger)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _webSocket = socket ?? throw new ArgumentNullException(nameof(socket));
+ }
+
+ /// <summary>
+ /// Gets or sets the state.
+ /// </summary>
+ /// <value>The state.</value>
+ public WebSocketState State => _webSocket.State;
+
+ /// <summary>
+ /// Sends the async.
+ /// </summary>
+ /// <param name="bytes">The bytes.</param>
+ /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken)
+ {
+ return _webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken);
+ }
+
+ /// <summary>
+ /// Sends the asynchronous.
+ /// </summary>
+ /// <param name="text">The text.</param>
+ /// <param name="endOfMessage">if set to <c>true</c> [end of message].</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken)
+ {
+ return _webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken);
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool dispose)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (dispose)
+ {
+ _cancellationTokenSource.Cancel();
+ if (_webSocket.State == WebSocketState.Open)
+ {
+ _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client",
+ CancellationToken.None);
+ }
+ Closed?.Invoke(this, EventArgs.Empty);
+ }
+
+ _disposed = true;
+ }
+
+ /// <summary>
+ /// Gets or sets the receive action.
+ /// </summary>
+ /// <value>The receive action.</value>
+ public Action<byte[]> OnReceiveBytes { get; set; }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
new file mode 100644
index 000000000..dd313b336
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.WebSockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Emby.Server.Implementations.HttpServer;
+using Emby.Server.Implementations.Net;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public class WebSocketSharpListener : IHttpListener
+ {
+ private readonly ILogger _logger;
+
+ private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
+ private CancellationToken _disposeCancellationToken;
+
+ public WebSocketSharpListener(
+ ILogger logger)
+ {
+ _logger = logger;
+
+ _disposeCancellationToken = _disposeCancellationTokenSource.Token;
+ }
+
+ public Func<Exception, IRequest, bool, bool, Task> ErrorHandler { get; set; }
+ public Func<IHttpRequest, string, string, string, CancellationToken, Task> RequestHandler { get; set; }
+
+ public Action<WebSocketConnectEventArgs> WebSocketConnected { get; set; }
+
+ private static void LogRequest(ILogger logger, HttpRequest request)
+ {
+ var url = request.GetDisplayUrl();
+
+ logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString());
+ }
+
+ public async Task ProcessWebSocketRequest(HttpContext ctx)
+ {
+ try
+ {
+ LogRequest(_logger, ctx.Request);
+ var endpoint = ctx.Connection.RemoteIpAddress.ToString();
+ var url = ctx.Request.GetDisplayUrl();
+
+ var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false);
+ var socket = new SharpWebSocket(webSocketContext, _logger);
+
+ WebSocketConnected(new WebSocketConnectEventArgs
+ {
+ Url = url,
+ QueryString = ctx.Request.Query,
+ WebSocket = socket,
+ Endpoint = endpoint
+ });
+
+ WebSocketReceiveResult result;
+ var message = new List<byte>();
+
+ do
+ {
+ var buffer = WebSocket.CreateServerBuffer(4096);
+ result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken);
+ message.AddRange(buffer.Array.Take(result.Count));
+
+ if (result.EndOfMessage)
+ {
+ socket.OnReceiveBytes(message.ToArray());
+ message.Clear();
+ }
+ } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close);
+
+
+ if (webSocketContext.State == WebSocketState.Open)
+ {
+ await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
+ result.CloseStatusDescription, _disposeCancellationToken);
+ }
+
+ socket.Dispose();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "AcceptWebSocketAsync error");
+ if (!ctx.Response.HasStarted)
+ {
+ ctx.Response.StatusCode = 500;
+ }
+ }
+ }
+
+ public Task Stop()
+ {
+ _disposeCancellationTokenSource.Cancel();
+ return Task.CompletedTask;
+ }
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ private bool _disposed;
+
+ /// <summary>
+ /// Releases the unmanaged resources and disposes of the managed resources used.
+ /// </summary>
+ /// <param name="disposing">Whether or not the managed resources should be disposed</param>
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ Stop().GetAwaiter().GetResult();
+ }
+
+ _disposed = true;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
new file mode 100644
index 000000000..e0a0ee286
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
@@ -0,0 +1,518 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Text;
+using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+using IHttpFile = MediaBrowser.Model.Services.IHttpFile;
+using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
+using IResponse = MediaBrowser.Model.Services.IResponse;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public partial class WebSocketSharpRequest : IHttpRequest
+ {
+ private readonly HttpRequest request;
+
+ public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger)
+ {
+ this.OperationName = operationName;
+ this.request = httpContext;
+ this.Response = new WebSocketSharpResponse(logger, response);
+
+ // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]);
+ }
+
+ public HttpRequest HttpRequest => request;
+
+ public IResponse Response { get; }
+
+ public string OperationName { get; set; }
+
+ public object Dto { get; set; }
+
+ public string RawUrl => request.GetEncodedPathAndQuery();
+
+ public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/');
+
+ public string XForwardedFor
+ => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString();
+
+ public int? XForwardedPort
+ => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture);
+
+ public string XForwardedProtocol => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"].ToString();
+
+ public string XRealIp => StringValues.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"].ToString();
+
+ private string remoteIp;
+ public string RemoteIp
+ {
+ get
+ {
+ if (remoteIp != null)
+ {
+ return remoteIp;
+ }
+
+ var temp = CheckBadChars(XForwardedFor.AsSpan());
+ if (temp.Length != 0)
+ {
+ return remoteIp = temp.ToString();
+ }
+
+ temp = CheckBadChars(XRealIp.AsSpan());
+ if (temp.Length != 0)
+ {
+ return remoteIp = NormalizeIp(temp).ToString();
+ }
+
+ return remoteIp = NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString().AsSpan()).ToString();
+ }
+ }
+
+ private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
+
+ // CheckBadChars - throws on invalid chars to be not found in header name/value
+ internal static ReadOnlySpan<char> CheckBadChars(ReadOnlySpan<char> name)
+ {
+ if (name.Length == 0)
+ {
+ return name;
+ }
+
+ // VALUE check
+ // Trim spaces from both ends
+ name = name.Trim(HttpTrimCharacters);
+
+ // First, check for correctly formed multi-line value
+ // Second, check for absence of CTL characters
+ int crlf = 0;
+ for (int i = 0; i < name.Length; ++i)
+ {
+ char c = (char)(0x000000ff & (uint)name[i]);
+ switch (crlf)
+ {
+ case 0:
+ {
+ if (c == '\r')
+ {
+ crlf = 1;
+ }
+ else if (c == '\n')
+ {
+ // Technically this is bad HTTP. But it would be a breaking change to throw here.
+ // Is there an exploit?
+ crlf = 2;
+ }
+ else if (c == 127 || (c < ' ' && c != '\t'))
+ {
+ throw new ArgumentException("net_WebHeaderInvalidControlChars", nameof(name));
+ }
+
+ break;
+ }
+
+ case 1:
+ {
+ if (c == '\n')
+ {
+ crlf = 2;
+ break;
+ }
+
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name));
+ }
+
+ case 2:
+ {
+ if (c == ' ' || c == '\t')
+ {
+ crlf = 0;
+ break;
+ }
+
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name));
+ }
+ }
+ }
+
+ if (crlf != 0)
+ {
+ throw new ArgumentException("net_WebHeaderInvalidCRLFChars", nameof(name));
+ }
+
+ return name;
+ }
+
+ private ReadOnlySpan<char> NormalizeIp(ReadOnlySpan<char> ip)
+ {
+ if (ip.Length != 0 && !ip.IsWhiteSpace())
+ {
+ // Handle ipv4 mapped to ipv6
+ const string srch = "::ffff:";
+ var index = ip.IndexOf(srch.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ if (index == 0)
+ {
+ ip = ip.Slice(srch.Length);
+ }
+ }
+
+ return ip;
+ }
+
+ public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
+
+ private Dictionary<string, object> items;
+ public Dictionary<string, object> Items => items ?? (items = new Dictionary<string, object>());
+
+ private string responseContentType;
+ public string ResponseContentType
+ {
+ get =>
+ responseContentType
+ ?? (responseContentType = GetResponseContentType(HttpRequest));
+ set => this.responseContentType = value;
+ }
+
+ public const string FormUrlEncoded = "application/x-www-form-urlencoded";
+ public const string MultiPartFormData = "multipart/form-data";
+ public static string GetResponseContentType(HttpRequest httpReq)
+ {
+ var specifiedContentType = GetQueryStringContentType(httpReq);
+ if (!string.IsNullOrEmpty(specifiedContentType))
+ {
+ return specifiedContentType;
+ }
+
+ const string serverDefaultContentType = "application/json";
+
+ var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept);
+ string defaultContentType = null;
+ if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData))
+ {
+ defaultContentType = serverDefaultContentType;
+ }
+
+ var acceptsAnything = false;
+ var hasDefaultContentType = defaultContentType != null;
+ if (acceptContentTypes != null)
+ {
+ foreach (var acceptsType in acceptContentTypes)
+ {
+ // TODO: @bond move to Span when Span.Split lands
+ // https://github.com/dotnet/corefx/issues/26528
+ var contentType = acceptsType?.Split(';')[0].Trim();
+ acceptsAnything = contentType.Equals("*/*", StringComparison.OrdinalIgnoreCase);
+
+ if (acceptsAnything)
+ {
+ break;
+ }
+ }
+
+ if (acceptsAnything)
+ {
+ if (hasDefaultContentType)
+ {
+ return defaultContentType;
+ }
+ else
+ {
+ return serverDefaultContentType;
+ }
+ }
+ }
+
+ if (acceptContentTypes == null && httpReq.ContentType == Soap11)
+ {
+ return Soap11;
+ }
+
+ // We could also send a '406 Not Acceptable', but this is allowed also
+ return serverDefaultContentType;
+ }
+
+ public const string Soap11 = "text/xml; charset=utf-8";
+
+ public static bool HasAnyOfContentTypes(HttpRequest request, params string[] contentTypes)
+ {
+ if (contentTypes == null || request.ContentType == null)
+ {
+ return false;
+ }
+
+ foreach (var contentType in contentTypes)
+ {
+ if (IsContentType(request, contentType))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool IsContentType(HttpRequest request, string contentType)
+ {
+ return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private static string GetQueryStringContentType(HttpRequest httpReq)
+ {
+ ReadOnlySpan<char> format = httpReq.Query["format"].ToString().AsSpan();
+ if (format == null)
+ {
+ const int formatMaxLength = 4;
+ ReadOnlySpan<char> pi = httpReq.Path.ToString().AsSpan();
+ if (pi == null || pi.Length <= formatMaxLength)
+ {
+ return null;
+ }
+
+ if (pi[0] == '/')
+ {
+ pi = pi.Slice(1);
+ }
+
+ format = LeftPart(pi, '/');
+ if (format.Length > formatMaxLength)
+ {
+ return null;
+ }
+ }
+
+ format = LeftPart(format, '.');
+ if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ return "application/json";
+ }
+ else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ return "application/xml";
+ }
+
+ return null;
+ }
+
+ public static ReadOnlySpan<char> LeftPart(ReadOnlySpan<char> strVal, char needle)
+ {
+ if (strVal == null)
+ {
+ return null;
+ }
+
+ var pos = strVal.IndexOf(needle);
+ return pos == -1 ? strVal : strVal.Slice(0, pos);
+ }
+
+ public static string HandlerFactoryPath;
+
+ private string pathInfo;
+ public string PathInfo
+ {
+ get
+ {
+ if (this.pathInfo == null)
+ {
+ var mode = HandlerFactoryPath;
+
+ var pos = RawUrl.IndexOf("?", StringComparison.Ordinal);
+ if (pos != -1)
+ {
+ var path = RawUrl.Substring(0, pos);
+ this.pathInfo = GetPathInfo(
+ path,
+ mode,
+ mode ?? string.Empty);
+ }
+ else
+ {
+ this.pathInfo = RawUrl;
+ }
+
+ this.pathInfo = WebUtility.UrlDecode(pathInfo);
+ this.pathInfo = NormalizePathInfo(pathInfo, mode).ToString();
+ }
+
+ return this.pathInfo;
+ }
+ }
+
+ private static string GetPathInfo(string fullPath, string mode, string appPath)
+ {
+ var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
+ if (!string.IsNullOrEmpty(pathInfo))
+ {
+ return pathInfo;
+ }
+
+ // Wildcard mode relies on this to work out the handlerPath
+ pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
+ if (!string.IsNullOrEmpty(pathInfo))
+ {
+ return pathInfo;
+ }
+
+ return fullPath;
+ }
+
+ private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
+ {
+ if (mappedPathRoot == null)
+ {
+ return null;
+ }
+
+ var sbPathInfo = new StringBuilder();
+ var fullPathParts = fullPath.Split('/');
+ var mappedPathRootParts = mappedPathRoot.Split('/');
+ var fullPathIndexOffset = mappedPathRootParts.Length - 1;
+ var pathRootFound = false;
+
+ for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++)
+ {
+ if (pathRootFound)
+ {
+ sbPathInfo.Append("/" + fullPathParts[fullPathIndex]);
+ }
+ else if (fullPathIndex - fullPathIndexOffset >= 0)
+ {
+ pathRootFound = true;
+ for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++)
+ {
+ if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase))
+ {
+ pathRootFound = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!pathRootFound)
+ {
+ return null;
+ }
+
+ return sbPathInfo.Length > 1 ? sbPathInfo.ToString().TrimEnd('/') : "/";
+ }
+
+ public string UserAgent => request.Headers[HeaderNames.UserAgent];
+
+ public IHeaderDictionary Headers => request.Headers;
+
+ public IQueryCollection QueryString => request.Query;
+
+ public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString());
+
+ private string httpMethod;
+ public string HttpMethod =>
+ httpMethod
+ ?? (httpMethod = request.Method);
+
+ public string Verb => HttpMethod;
+
+ public string ContentType => request.ContentType;
+
+ private Encoding ContentEncoding
+ {
+ get
+ {
+ // TODO is this necessary?
+ if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP"))
+ {
+ string postDataCharset = Headers["x-up-devcap-post-charset"];
+ if (!string.IsNullOrEmpty(postDataCharset))
+ {
+ try
+ {
+ return Encoding.GetEncoding(postDataCharset);
+ }
+ catch (ArgumentException)
+ {
+ }
+ }
+ }
+
+ return request.GetTypedHeaders().ContentType.Encoding ?? Encoding.UTF8;
+ }
+ }
+
+ public Uri UrlReferrer => request.GetTypedHeaders().Referer;
+
+ public static Encoding GetEncoding(string contentTypeHeader)
+ {
+ var param = GetParameter(contentTypeHeader.AsSpan(), "charset=");
+ if (param == null)
+ {
+ return null;
+ }
+
+ try
+ {
+ return Encoding.GetEncoding(param);
+ }
+ catch (ArgumentException)
+ {
+ return null;
+ }
+ }
+
+ public Stream InputStream => request.Body;
+
+ public long ContentLength => request.ContentLength ?? 0;
+
+ private IHttpFile[] httpFiles;
+ public IHttpFile[] Files
+ {
+ get
+ {
+ if (httpFiles == null)
+ {
+ if (files == null)
+ {
+ return httpFiles = Array.Empty<IHttpFile>();
+ }
+
+ httpFiles = new IHttpFile[files.Count];
+ var i = 0;
+ foreach (var pair in files)
+ {
+ var reqFile = pair.Value;
+ httpFiles[i] = new HttpFile
+ {
+ ContentType = reqFile.ContentType,
+ ContentLength = reqFile.ContentLength,
+ FileName = reqFile.FileName,
+ InputStream = reqFile.InputStream,
+ };
+ i++;
+ }
+ }
+
+ return httpFiles;
+ }
+ }
+
+ public static ReadOnlySpan<char> NormalizePathInfo(string pathInfo, string handlerPath)
+ {
+ if (handlerPath != null)
+ {
+ var trimmed = pathInfo.AsSpan().TrimStart('/');
+ if (trimmed.StartsWith(handlerPath.AsSpan(), StringComparison.OrdinalIgnoreCase))
+ {
+ return trimmed.Slice(handlerPath.Length).ToString().AsSpan();
+ }
+ }
+
+ return pathInfo.AsSpan();
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs
new file mode 100644
index 000000000..0f67eaa62
--- /dev/null
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using IRequest = MediaBrowser.Model.Services.IRequest;
+
+namespace Emby.Server.Implementations.SocketSharp
+{
+ public class WebSocketSharpResponse : IResponse
+ {
+ private readonly ILogger _logger;
+
+ public WebSocketSharpResponse(ILogger logger, HttpResponse response)
+ {
+ _logger = logger;
+ OriginalResponse = response;
+ }
+
+ public HttpResponse OriginalResponse { get; }
+
+ public int StatusCode
+ {
+ get => OriginalResponse.StatusCode;
+ set => OriginalResponse.StatusCode = value;
+ }
+
+ public string StatusDescription { get; set; }
+
+ public string ContentType
+ {
+ get => OriginalResponse.ContentType;
+ set => OriginalResponse.ContentType = value;
+ }
+
+ public void AddHeader(string name, string value)
+ {
+ if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase))
+ {
+ ContentType = value;
+ return;
+ }
+
+ OriginalResponse.Headers.Add(name, value);
+ }
+
+ public void Redirect(string url)
+ {
+ OriginalResponse.Redirect(url);
+ }
+
+ public Stream OutputStream => OriginalResponse.Body;
+
+ public bool SendChunked { get; set; }
+
+ const int StreamCopyToBufferSize = 81920;
+ public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken)
+ {
+ var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+
+ //if (count <= 0)
+ //{
+ // allowAsync = true;
+ //}
+
+ var fileOpenOptions = FileOpenOptions.SequentialScan;
+
+ if (allowAsync)
+ {
+ fileOpenOptions |= FileOpenOptions.Asynchronous;
+ }
+
+ // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
+
+ using (var fs = fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
+ {
+ if (offset > 0)
+ {
+ fs.Position = offset;
+ }
+
+ if (count > 0)
+ {
+ await streamHelper.CopyToAsync(fs, OutputStream, count, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await fs.CopyToAsync(OutputStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
index ce6c2cd87..a3f3f6cb4 100644
--- a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using Emby.Server.Implementations.Images;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing;
@@ -20,7 +19,7 @@ namespace Emby.Server.Implementations.UserViews
{
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var view = (CollectionFolder)item;
var viewType = view.CollectionType;
@@ -56,7 +55,7 @@ namespace Emby.Server.Implementations.UserViews
includeItemTypes = new string[] { "Video", "Audio", "Photo", "Movie", "Series" };
}
- var recursive = !new[] { CollectionType.Playlists }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
return view.GetItemList(new InternalItemsQuery
{
@@ -71,7 +70,7 @@ namespace Emby.Server.Implementations.UserViews
},
IncludeItemTypes = includeItemTypes
- }).ToList();
+ });
}
protected override bool Supports(BaseItem item)
@@ -79,7 +78,7 @@ namespace Emby.Server.Implementations.UserViews
return item is CollectionFolder;
}
- protected override string CreateImage(BaseItem item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
diff --git a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs
index 4ec68e550..f48520443 100644
--- a/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/Emby.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -28,7 +28,7 @@ namespace Emby.Server.Implementations.UserViews
_libraryManager = libraryManager;
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var view = (UserView)item;
@@ -46,8 +46,7 @@ namespace Emby.Server.Implementations.UserViews
var items = result.Select(i =>
{
- var episode = i as Episode;
- if (episode != null)
+ if (i is Episode episode)
{
var series = episode.Series;
if (series != null)
@@ -58,8 +57,7 @@ namespace Emby.Server.Implementations.UserViews
return episode;
}
- var season = i as Season;
- if (season != null)
+ if (i is Season season)
{
var series = season.Series;
if (series != null)
@@ -70,8 +68,7 @@ namespace Emby.Server.Implementations.UserViews
return season;
}
- var audio = i as Audio;
- if (audio != null)
+ if (i is Audio audio)
{
var album = audio.AlbumEntity;
if (album != null && album.HasImage(ImageType.Primary))
@@ -122,7 +119,7 @@ namespace Emby.Server.Implementations.UserViews
return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
}
- protected override string CreateImage(BaseItem item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
if (itemsWithImages.Count == 0)
{
diff --git a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs
index c810004ab..4655cd928 100644
--- a/Emby.Server.Implementations/UserViews/FolderImageProvider.cs
+++ b/Emby.Server.Implementations/UserViews/FolderImageProvider.cs
@@ -24,7 +24,7 @@ namespace Emby.Server.Implementations.UserViews
_libraryManager = libraryManager;
}
- protected override List<BaseItem> GetItemsWithImages(BaseItem item)
+ protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
return _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.UserViews
});
}
- protected override string CreateImage(BaseItem item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
{
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
}
diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs
new file mode 100644
index 000000000..eb1877440
--- /dev/null
+++ b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using MediaBrowser.Model.Net;
+
+namespace Emby.Server.Implementations.WebSockets
+{
+ public interface IWebSocketHandler
+ {
+ Task ProcessMessage(WebSocketMessage<object> message, TaskCompletionSource<bool> taskCompletionSource);
+ }
+}
diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs
new file mode 100644
index 000000000..04c73ecea
--- /dev/null
+++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
+using Microsoft.Extensions.Logging;
+using UtfUnknown;
+
+namespace Emby.Server.Implementations.WebSockets
+{
+ public class WebSocketManager
+ {
+ private readonly IWebSocketHandler[] _webSocketHandlers;
+ private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger<WebSocketManager> _logger;
+ private const int BufferSize = 4096;
+
+ public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger<WebSocketManager> logger)
+ {
+ _webSocketHandlers = webSocketHandlers;
+ _jsonSerializer = jsonSerializer;
+ _logger = logger;
+ }
+
+ public async Task OnWebSocketConnected(WebSocket webSocket)
+ {
+ var taskCompletionSource = new TaskCompletionSource<bool>();
+ var cancellationToken = new CancellationTokenSource().Token;
+ WebSocketReceiveResult result;
+ var message = new List<byte>();
+
+ // Keep listening for incoming messages, otherwise the socket closes automatically
+ do
+ {
+ var buffer = WebSocket.CreateServerBuffer(BufferSize);
+ result = await webSocket.ReceiveAsync(buffer, cancellationToken);
+ message.AddRange(buffer.Array.Take(result.Count));
+
+ if (result.EndOfMessage)
+ {
+ await ProcessMessage(message.ToArray(), taskCompletionSource);
+ message.Clear();
+ }
+ } while (!taskCompletionSource.Task.IsCompleted &&
+ webSocket.State == WebSocketState.Open &&
+ result.MessageType != WebSocketMessageType.Close);
+
+ if (webSocket.State == WebSocketState.Open)
+ {
+ await webSocket.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
+ result.CloseStatusDescription, cancellationToken);
+ }
+ }
+
+ private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource<bool> taskCompletionSource)
+ {
+ var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName;
+ var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase)
+ ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length)
+ : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length);
+
+ // All messages are expected to be valid JSON objects
+ if (!message.StartsWith("{", StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Received web socket message that is not a json structure: {Message}", message);
+ return;
+ }
+
+ try
+ {
+ var info = _jsonSerializer.DeserializeFromString<WebSocketMessage<object>>(message);
+
+ _logger.LogDebug("Websocket message received: {0}", info.MessageType);
+
+ var tasks = _webSocketHandlers.Select(handler => Task.Run(() =>
+ {
+ try
+ {
+ handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}",
+ handler.GetType().Name, info.MessageType ?? string.Empty);
+ }
+ }));
+
+ await Task.WhenAll(tasks);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error processing web socket message");
+ }
+ }
+ }
+}