From 49d9649b8eb7e9edc889c10a679e980486ee0018 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 30 Jan 2019 23:35:17 -0800 Subject: [PATCH 001/166] added submodule dir to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index aef6662727..3bea16e5d2 100644 --- a/.gitignore +++ b/.gitignore @@ -264,3 +264,4 @@ deployment/**/pkg-dist-tmp/ deployment/collect-dist/ jellyfin_version.ini +MediaBrowser.WebDashboard/jellyfin-web From 4519ce26e2250cb233836296d292ddb7b3cf6346 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Thu, 31 Jan 2019 00:24:53 -0800 Subject: [PATCH 002/166] Upgrade crypto provider, retarget better framework --- .../Cryptography/CryptographyProvider.cs | 171 ++++++++++++++---- .../Emby.Server.Implementations.csproj | 2 +- .../Library/UserManager.cs | 31 ++-- .../Cryptography/ICryptoProvider.cs | 8 + .../Cryptography/PasswordHash.cs | 93 ++++++++++ 5 files changed, 248 insertions(+), 57 deletions(-) create mode 100644 MediaBrowser.Model/Cryptography/PasswordHash.cs diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 09fdbc856d..ca6ae2bb2d 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,40 +1,131 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Cryptography -{ - public class CryptographyProvider : ICryptoProvider - { - public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } - - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Cryptography +{ + public class CryptographyProvider : ICryptoProvider + { + private List SupportedHashMethods = new List(); + private string DefaultHashMethod = "SHA256"; + private RandomNumberGenerator rng; + private int defaultiterations = 1000; + public CryptographyProvider() + { + //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 + SupportedHashMethods = new List + { + "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" + }; + rng = RandomNumberGenerator.Create(); + } + + public Guid GetMD5(string str) + { + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + } + + public byte[] ComputeSHA1(byte[] bytes) + { + using (var provider = SHA1.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public byte[] ComputeMD5(Stream str) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(str); + } + } + + public byte[] ComputeMD5(byte[] bytes) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public IEnumerable GetSupportedHashMethods() + { + return SupportedHashMethods; + } + + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt) + { + using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method))) + { + return r.GetBytes(32); + } + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes) + { + return ComputeHash(HashMethod, bytes, new byte[0]); + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes) + { + return ComputeHash(DefaultHashMethod, bytes); + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + { + if (SupportedHashMethods.Contains(HashMethod)) + { + if (salt.Length == 0) + { + using (var h = HashAlgorithm.Create(HashMethod)) + { + return h.ComputeHash(bytes); + } + } + else + { + return PBKDF2(HashMethod, bytes, salt); + } + } + else + { + throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); + } + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) + { + return PBKDF2(DefaultHashMethod, bytes, salt); + } + + public byte[] ComputeHash(PasswordHash hash) + { + return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes); + } + + public byte[] GenerateSalt() + { + byte[] salt = new byte[8]; + rng.GetBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 3aa617b021..df7963b023 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -35,7 +35,7 @@ - netstandard2.0 + netcoreapp2.1 false diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 05fce4542f..70639dad5f 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; @@ -220,22 +221,20 @@ namespace Emby.Server.Implementations.Library } } - public 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; - } - - private static bool IsValidUsernameCharacter(char i) - { - return !char.Equals(i, '<') && !char.Equals(i, '>'); + public bool IsValidUsername(string username) + { + //The old way was dumb, we should make it less dumb, lets do so. + //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 + string UserNameRegex = "^[\\w-'._@]*$"; + // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + return Regex.IsMatch(username, UserNameRegex); + } + + private static bool IsValidUsernameCharacter(char i) + { + string UserNameRegex = "^[\\w-'._@]*$"; + return Regex.IsMatch(i.ToString(), UserNameRegex); } public string MakeValidUsername(string username) diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index b027d2ad0b..ec7e57fec5 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Collections.Generic; namespace MediaBrowser.Model.Cryptography { @@ -9,5 +10,12 @@ namespace MediaBrowser.Model.Cryptography byte[] ComputeMD5(Stream str); byte[] ComputeMD5(byte[] bytes); byte[] ComputeSHA1(byte[] bytes); + IEnumerable GetSupportedHashMethods(); + byte[] ComputeHash(string HashMethod, byte[] bytes); + byte[] ComputeHashWithDefaultMethod(byte[] bytes); + byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); + byte[] ComputeHash(PasswordHash hash); + byte[] GenerateSalt(); } } diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs new file mode 100644 index 0000000000..d37220ab2f --- /dev/null +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MediaBrowser.Model.Cryptography +{ + public class PasswordHash + { + //Defined from this hash storage spec + //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + //$[$=(,=)*][$[$]] + + public string Id; + public Dictionary Parameters = new Dictionary(); + public string Salt; + public byte[] SaltBytes; + public string Hash; + public byte[] HashBytes; + public PasswordHash(string StorageString) + { + string[] a = StorageString.Split('$'); + Id = a[1]; + if (a[2].Contains("=")) + { + foreach (string paramset in (a[2].Split(','))) + { + if (!String.IsNullOrEmpty(paramset)) + { + string[] fields = paramset.Split('='); + Parameters.Add(fields[0], fields[1]); + } + } + if (a.Length == 4) + { + Salt = a[2]; + SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + Hash = a[3]; + HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + } + else + { + Salt = string.Empty; + Hash = a[3]; + HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + } + } + else + { + if (a.Length == 4) + { + Salt = a[2]; + SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + Hash = a[3]; + HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + } + else + { + Salt = string.Empty; + Hash = a[2]; + HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + } + + } + + } + + public PasswordHash(ICryptoProvider cryptoProvider2) + { + Id = "SHA256"; + SaltBytes = cryptoProvider2.GenerateSalt(); + Salt = Convert.ToBase64String(SaltBytes); + } + private string SerializeParameters() + { + string ReturnString = String.Empty; + foreach (var KVP in Parameters) + { + ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); + } + if (ReturnString[0] == ',') + { + ReturnString = ReturnString.Remove(0, 1); + } + return ReturnString; + } + + public override string ToString() + { + return String.Format("${0}${1}${2}${3}", Id, SerializeParameters(), Salt, Hash); + } + } + +} From 05bbf71b6d97614888efe103f763753e4487cc2c Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 02:16:03 -0800 Subject: [PATCH 003/166] sha256 with salt auth and sha1 interop --- .../Cryptography/CryptographyProvider.cs | 2 +- .../Library/DefaultAuthenticationProvider.cs | 169 +- .../Library/UserManager.cs | 2416 ++++++++--------- .../Cryptography/ICryptoProvider.cs | 29 +- .../Cryptography/PasswordHash.cs | 37 +- 5 files changed, 1389 insertions(+), 1264 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index ca6ae2bb2d..4f2bc1b030 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -10,7 +10,7 @@ namespace Emby.Server.Implementations.Cryptography public class CryptographyProvider : ICryptoProvider { private List SupportedHashMethods = new List(); - private string DefaultHashMethod = "SHA256"; + public string DefaultHashMethod => "SHA256"; private RandomNumberGenerator rng; private int defaultiterations = 1000; public CryptographyProvider() diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 4013ac0c80..92346c65aa 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; @@ -19,31 +20,110 @@ namespace Emby.Server.Implementations.Library 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 Authenticate(string username, string password) { throw new NotImplementedException(); } - public Task Authenticate(string username, string password, User resolvedUser) - { - if (resolvedUser == null) - { - throw new Exception("Invalid username or password"); - } - var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - - if (!success) - { - throw new Exception("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); + //This is the verson that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, User resolvedUser) + { + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + bool success = false; + if (resolvedUser == null) + { + success = false; + throw new Exception("Invalid username or password"); + } + if (!resolvedUser.Password.Contains("$")) + { + ConvertPasswordFormat(resolvedUser); + } + PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password); + byte[] CalculatedHash; + string CalculatedHashString; + if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == 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 + { + success = false; + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id)); + } + + //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + + if (!success) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); } + //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)) + { + 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); + } + } + } + + // OLD VERSION //public Task Authenticate(string username, string password, User resolvedUser) + // OLD VERSION //{ + // OLD VERSION // if (resolvedUser == null) + // OLD VERSION // { + // OLD VERSION // throw new Exception("Invalid username or password"); + // OLD VERSION // } + // OLD VERSION // + // OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); + // OLD VERSION // + // OLD VERSION // if (!success) + // OLD VERSION // { + // OLD VERSION // throw new Exception("Invalid username or password"); + // OLD VERSION // } + // OLD VERSION // + // OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult + // OLD VERSION // { + // OLD VERSION // Username = username + // OLD VERSION // }); + // OLD VERSION //} + public Task HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); @@ -57,19 +137,26 @@ namespace Emby.Server.Implementations.Library public Task ChangePassword(User user, string newPassword) { - string newPasswordHash = null; - - if (newPassword != null) + //string newPasswordHash = null; + ConvertPasswordFormat(user); + PasswordHash passwordHash = new PasswordHash(user.Password); + if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { - newPasswordHash = GetHashedString(user, newPassword); + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-",""); + 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; } @@ -86,19 +173,39 @@ namespace Emby.Server.Implementations.Library return GetHashedString(user, string.Empty); } + public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash) + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty); + } + /// /// Gets the hashed string. /// - public string GetHashedString(User user, string str) - { - var salt = user.Salt; - if (salt != null) + public string GetHashedString(User user, string str) + { + //This is legacy. Deprecated in the auth method. + //return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + PasswordHash passwordHash; + if (String.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else { - // return BCrypt.HashPassword(str, salt); + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); + } + if (passwordHash.SaltBytes != null) + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty); + } + else + { + return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + //throw new Exception("User does not have a hash, this should not be possible"); } - // legacy - return BitConverter.ToString(_cryptographyProvider.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); + } } } diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 40eda52c6a..a139c4e736 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -1,222 +1,222 @@ -using System; -using System.Collections.Generic; -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; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Devices; -using MediaBrowser.Controller.Drawing; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Users; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations.Library -{ - /// - /// Class UserManager - /// - public class UserManager : IUserManager - { - /// - /// Gets the users. - /// - /// The users. - public IEnumerable Users => _users; - - private User[] _users; - - /// - /// The _logger - /// - private readonly ILogger _logger; - - /// - /// Gets or sets the configuration manager. - /// - /// The configuration manager. - private IServerConfigurationManager ConfigurationManager { get; set; } - - /// - /// Gets the active user repository - /// - /// The user repository. - private IUserRepository UserRepository { get; set; } - public event EventHandler> UserPasswordChanged; - - private readonly IXmlSerializer _xmlSerializer; - private readonly IJsonSerializer _jsonSerializer; - - private readonly INetworkManager _networkManager; - - private readonly Func _imageProcessorFactory; - private readonly Func _dtoServiceFactory; - private readonly IServerApplicationHost _appHost; - private readonly IFileSystem _fileSystem; - private readonly ICryptoProvider _cryptographyProvider; - - private IAuthenticationProvider[] _authenticationProviders; - private DefaultAuthenticationProvider _defaultAuthenticationProvider; - - public UserManager( - ILoggerFactory loggerFactory, - IServerConfigurationManager configurationManager, - IUserRepository userRepository, - IXmlSerializer xmlSerializer, - INetworkManager networkManager, - Func imageProcessorFactory, - Func dtoServiceFactory, - IServerApplicationHost appHost, - IJsonSerializer jsonSerializer, - IFileSystem fileSystem, - ICryptoProvider cryptographyProvider) - { - _logger = loggerFactory.CreateLogger(nameof(UserManager)); - UserRepository = userRepository; - _xmlSerializer = xmlSerializer; - _networkManager = networkManager; - _imageProcessorFactory = imageProcessorFactory; - _dtoServiceFactory = dtoServiceFactory; - _appHost = appHost; - _jsonSerializer = jsonSerializer; - _fileSystem = fileSystem; - _cryptographyProvider = cryptographyProvider; - ConfigurationManager = configurationManager; - _users = Array.Empty(); - - DeletePinFile(); - } - - public NameIdPair[] GetAuthenticationProviders() - { - return _authenticationProviders - .Where(i => i.IsEnabled) - .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) - .ThenBy(i => i.Name) - .Select(i => new NameIdPair - { - Name = i.Name, - Id = GetAuthenticationProviderId(i) - }) - .ToArray(); - } - - public void AddParts(IEnumerable authenticationProviders) - { - _authenticationProviders = authenticationProviders.ToArray(); - - _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); - } - - #region UserUpdated Event - /// - /// Occurs when [user updated]. - /// - public event EventHandler> UserUpdated; - public event EventHandler> UserPolicyUpdated; - public event EventHandler> UserConfigurationUpdated; - public event EventHandler> UserLockedOut; - - /// - /// Called when [user updated]. - /// - /// The user. - private void OnUserUpdated(User user) - { - UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - #region UserDeleted Event - /// - /// Occurs when [user deleted]. - /// - public event EventHandler> UserDeleted; - /// - /// Called when [user deleted]. - /// - /// The user. - private void OnUserDeleted(User user) - { - UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); - } - #endregion - - /// - /// Gets a User by Id - /// - /// The id. - /// User. - /// - public User GetUserById(Guid id) - { - if (id.Equals(Guid.Empty)) - { - throw new ArgumentNullException(nameof(id)); - } - - return Users.FirstOrDefault(u => u.Id == id); - } - - /// - /// Gets the user by identifier. - /// - /// The identifier. - /// User. - public User GetUserById(string id) - { - return GetUserById(new Guid(id)); - } - - public User GetUserByName(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); - } - - public void Initialize() - { - _users = LoadUsers(); - - var users = Users.ToList(); - - // If there are no local users with admin rights, make them all admins - if (!users.Any(i => i.Policy.IsAdministrator)) - { - foreach (var user in users) - { - user.Policy.IsAdministrator = true; - UpdateUserPolicy(user, user.Policy, false); - } - } - } - +using System; +using System.Collections.Generic; +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; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Devices; +using MediaBrowser.Controller.Drawing; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Controller.Security; +using MediaBrowser.Controller.Session; +using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.Users; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.Library +{ + /// + /// Class UserManager + /// + public class UserManager : IUserManager + { + /// + /// Gets the users. + /// + /// The users. + public IEnumerable Users => _users; + + private User[] _users; + + /// + /// The _logger + /// + private readonly ILogger _logger; + + /// + /// Gets or sets the configuration manager. + /// + /// The configuration manager. + private IServerConfigurationManager ConfigurationManager { get; set; } + + /// + /// Gets the active user repository + /// + /// The user repository. + private IUserRepository UserRepository { get; set; } + public event EventHandler> UserPasswordChanged; + + private readonly IXmlSerializer _xmlSerializer; + private readonly IJsonSerializer _jsonSerializer; + + private readonly INetworkManager _networkManager; + + private readonly Func _imageProcessorFactory; + private readonly Func _dtoServiceFactory; + private readonly IServerApplicationHost _appHost; + private readonly IFileSystem _fileSystem; + private readonly ICryptoProvider _cryptographyProvider; + + private IAuthenticationProvider[] _authenticationProviders; + private DefaultAuthenticationProvider _defaultAuthenticationProvider; + + public UserManager( + ILoggerFactory loggerFactory, + IServerConfigurationManager configurationManager, + IUserRepository userRepository, + IXmlSerializer xmlSerializer, + INetworkManager networkManager, + Func imageProcessorFactory, + Func dtoServiceFactory, + IServerApplicationHost appHost, + IJsonSerializer jsonSerializer, + IFileSystem fileSystem, + ICryptoProvider cryptographyProvider) + { + _logger = loggerFactory.CreateLogger(nameof(UserManager)); + UserRepository = userRepository; + _xmlSerializer = xmlSerializer; + _networkManager = networkManager; + _imageProcessorFactory = imageProcessorFactory; + _dtoServiceFactory = dtoServiceFactory; + _appHost = appHost; + _jsonSerializer = jsonSerializer; + _fileSystem = fileSystem; + _cryptographyProvider = cryptographyProvider; + ConfigurationManager = configurationManager; + _users = Array.Empty(); + + DeletePinFile(); + } + + public NameIdPair[] GetAuthenticationProviders() + { + return _authenticationProviders + .Where(i => i.IsEnabled) + .OrderBy(i => i is DefaultAuthenticationProvider ? 0 : 1) + .ThenBy(i => i.Name) + .Select(i => new NameIdPair + { + Name = i.Name, + Id = GetAuthenticationProviderId(i) + }) + .ToArray(); + } + + public void AddParts(IEnumerable authenticationProviders) + { + _authenticationProviders = authenticationProviders.ToArray(); + + _defaultAuthenticationProvider = _authenticationProviders.OfType().First(); + } + + #region UserUpdated Event + /// + /// Occurs when [user updated]. + /// + public event EventHandler> UserUpdated; + public event EventHandler> UserPolicyUpdated; + public event EventHandler> UserConfigurationUpdated; + public event EventHandler> UserLockedOut; + + /// + /// Called when [user updated]. + /// + /// The user. + private void OnUserUpdated(User user) + { + UserUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + #region UserDeleted Event + /// + /// Occurs when [user deleted]. + /// + public event EventHandler> UserDeleted; + /// + /// Called when [user deleted]. + /// + /// The user. + private void OnUserDeleted(User user) + { + UserDeleted?.Invoke(this, new GenericEventArgs { Argument = user }); + } + #endregion + + /// + /// Gets a User by Id + /// + /// The id. + /// User. + /// + public User GetUserById(Guid id) + { + if (id.Equals(Guid.Empty)) + { + throw new ArgumentNullException(nameof(id)); + } + + return Users.FirstOrDefault(u => u.Id == id); + } + + /// + /// Gets the user by identifier. + /// + /// The identifier. + /// User. + public User GetUserById(string id) + { + return GetUserById(new Guid(id)); + } + + public User GetUserByName(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + return Users.FirstOrDefault(u => string.Equals(u.Name, name, StringComparison.OrdinalIgnoreCase)); + } + + public void Initialize() + { + _users = LoadUsers(); + + var users = Users.ToList(); + + // If there are no local users with admin rights, make them all admins + if (!users.Any(i => i.Policy.IsAdministrator)) + { + foreach (var user in users) + { + user.Policy.IsAdministrator = true; + UpdateUserPolicy(user, user.Policy, false); + } + } + } + public bool IsValidUsername(string username) { //The old way was dumb, we should make it less dumb, lets do so. @@ -231,992 +231,992 @@ namespace Emby.Server.Implementations.Library { string UserNameRegex = "^[\\w-'._@]*$"; return Regex.IsMatch(i.ToString(), UserNameRegex); - } - - public string MakeValidUsername(string username) - { - if (IsValidUsername(username)) - { - return username; - } - - // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - var builder = new StringBuilder(); - - foreach (var c in username) - { - if (IsValidUsernameCharacter(c)) - { - builder.Append(c); - } - } - return builder.ToString(); - } - - public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) - { - if (string.IsNullOrWhiteSpace(username)) - { - throw new ArgumentNullException(nameof(username)); - } - - var user = Users - .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); - - var success = false; - IAuthenticationProvider authenticationProvider = null; - - if (user != null) - { - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - } - else - { - // user is null - var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); - authenticationProvider = authResult.Item1; - success = authResult.Item2; - - if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) - { - user = await CreateUser(username).ConfigureAwait(false); - - var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; - if (hasNewUserPolicy != null) - { - var policy = hasNewUserPolicy.GetNewUserPolicy(); - UpdateUserPolicy(user, policy, true); - } - } - } - - if (success && user != null && authenticationProvider != null) - { - var providerId = GetAuthenticationProviderId(authenticationProvider); - - if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) - { - user.Policy.AuthenticationProviderId = providerId; - UpdateUserPolicy(user, user.Policy, true); - } - } - - if (user == null) - { - throw new SecurityException("Invalid username or password entered."); - } - - if (user.Policy.IsDisabled) - { - throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); - } - - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) - { - throw new SecurityException("Forbidden."); - } - - if (!user.IsParentalScheduleAllowed()) - { - throw new SecurityException("User is not allowed access at this time."); - } - - // Update LastActivityDate and LastLoginDate, then save - if (success) - { - if (isUserSession) - { - user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; - UpdateUser(user); - } - UpdateInvalidLoginAttemptCount(user, 0); - } - else - { - UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); - } - - _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); - - return success ? user : null; - } - - private static string GetAuthenticationProviderId(IAuthenticationProvider provider) - { - return provider.GetType().FullName; - } - - private IAuthenticationProvider GetAuthenticationProvider(User user) - { - return GetAuthenticationProviders(user).First(); - } - - private IAuthenticationProvider[] GetAuthenticationProviders(User user) - { - var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; - - var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); - - if (!string.IsNullOrEmpty(authenticationProviderId)) - { - providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); - } - - if (providers.Length == 0) - { - providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; - } - - return providers; - } - - private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) - { - try - { - var requiresResolvedUser = provider as IRequiresResolvedUser; - if (requiresResolvedUser != null) - { - await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); - } - else - { - await provider.Authenticate(username, password).ConfigureAwait(false); - } - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); - - return false; - } - } - - private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) - { - bool success = false; - IAuthenticationProvider authenticationProvider = null; - - if (password != null && user != null) - { - // Doesn't look like this is even possible to be used, because of password == null checks below - hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); - } - - if (password == null) - { - // legacy - success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - foreach (var provider in GetAuthenticationProviders(user)) - { - success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); - - if (success) - { - authenticationProvider = provider; - break; - } - } - } - - if (user != null) - { - if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) - { - if (password == null) - { - // legacy - success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); - } - else - { - success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); - } - } - } - - return new Tuple(authenticationProvider, success); - } - - private void UpdateInvalidLoginAttemptCount(User user, int newValue) - { - if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) - { - return; - } - - user.Policy.InvalidLoginAttemptCount = newValue; - - var maxCount = user.Policy.IsAdministrator ? 3 : 5; - - var fireLockout = false; - - if (newValue >= maxCount) - { - _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); - user.Policy.IsDisabled = true; - - fireLockout = true; - } - - UpdateUserPolicy(user, user.Policy, false); - - if (fireLockout) - { - UserLockedOut?.Invoke(this, new GenericEventArgs(user)); - } - } - - private string GetLocalPasswordHash(User user) - { - return string.IsNullOrEmpty(user.EasyPassword) - ? _defaultAuthenticationProvider.GetEmptyHashedString(user) - : user.EasyPassword; - } - - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); - } - - /// - /// Loads the users from the repository - /// - /// IEnumerable{User}. - private User[] LoadUsers() - { - var users = UserRepository.RetrieveAllUsers(); - - // There always has to be at least one user. - if (users.Count == 0) - { - var defaultName = Environment.UserName; - if (string.IsNullOrWhiteSpace(defaultName)) - { - defaultName = "MyJellyfinUser"; - } - var name = MakeValidUsername(defaultName); - - var user = InstantiateNewUser(name); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - users.Add(user); - - user.Policy.IsAdministrator = true; - user.Policy.EnableContentDeletion = true; - user.Policy.EnableRemoteControlOfOtherUsers = true; - UpdateUserPolicy(user, user.Policy, false); - } - - return users.ToArray(); - } - - public UserDto GetUserDto(User user, string remoteEndPoint = null) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; - var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); - - var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? - hasConfiguredEasyPassword : - hasConfiguredPassword; - - var dto = new UserDto - { - Id = user.Id, - Name = user.Name, - HasPassword = hasPassword, - HasConfiguredPassword = hasConfiguredPassword, - HasConfiguredEasyPassword = hasConfiguredEasyPassword, - LastActivityDate = user.LastActivityDate, - LastLoginDate = user.LastLoginDate, - Configuration = user.Configuration, - ServerId = _appHost.SystemId, - Policy = user.Policy - }; - - if (!hasPassword && Users.Count() == 1) - { - dto.EnableAutoLogin = true; - } - - var image = user.GetImageInfo(ImageType.Primary, 0); - - if (image != null) - { - dto.PrimaryImageTag = GetImageCacheTag(user, image); - - try - { - _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); - } - catch (Exception ex) - { - // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions - _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); - } - } - - return dto; - } - - public UserDto GetOfflineUserDto(User user) - { - var dto = GetUserDto(user); - - dto.ServerName = _appHost.FriendlyName; - - return dto; - } - - private string GetImageCacheTag(BaseItem item, ItemImageInfo image) - { - try - { - return _imageProcessorFactory().GetImageCacheTag(item, image); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); - return null; - } - } - - /// - /// Refreshes metadata for each user - /// - /// The cancellation token. - /// Task. - public async Task RefreshUsersMetadata(CancellationToken cancellationToken) - { - foreach (var user in Users) - { - await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Renames the user. - /// - /// The user. - /// The new name. - /// Task. - /// user - /// - public async Task RenameUser(User user, string newName) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } - - if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); - } - - if (user.Name.Equals(newName, StringComparison.Ordinal)) - { - throw new ArgumentException("The new and old names must be different."); - } - - await user.Rename(newName); - - OnUserUpdated(user); - } - - /// - /// Updates the user. - /// - /// The user. - /// user - /// - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) - { - throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); - } - - user.DateModified = DateTime.UtcNow; - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.UpdateUser(user); - - OnUserUpdated(user); - } - - public event EventHandler> UserCreated; - - private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); - - /// - /// Creates the user. - /// - /// The name. - /// User. - /// name - /// - public async Task CreateUser(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - throw new ArgumentNullException(nameof(name)); - } - - if (!IsValidUsername(name)) - { - throw new ArgumentException("Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); - } - - if (Users.Any(u => u.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); - } - - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - try - { - var user = InstantiateNewUser(name); - - var list = Users.ToList(); - list.Add(user); - _users = list.ToArray(); - - user.DateLastSaved = DateTime.UtcNow; - - UserRepository.CreateUser(user); - - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); - - return user; - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - /// - public async Task DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var allUsers = Users.ToList(); - - if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) - { - throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id)); - } - - if (allUsers.Count == 1) - { - throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name)); - } - - if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1) - { - throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name)); - } - - await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - - try - { - var configPath = GetConfigurationFilePath(user); - - UserRepository.DeleteUser(user); - - try - { - _fileSystem.DeleteFile(configPath); - } - catch (IOException ex) - { - _logger.LogError(ex, "Error deleting file {path}", configPath); - } - - DeleteUserPolicy(user); - - _users = allUsers.Where(i => i.Id != user.Id).ToArray(); - - OnUserDeleted(user); - } - finally - { - _userListLock.Release(); - } - } - - /// - /// Resets the password by clearing it. - /// - /// Task. - public Task ResetPassword(User user) - { - return ChangePassword(user, string.Empty); - } - - public void ResetEasyPassword(User user) - { - ChangeEasyPassword(user, string.Empty, null); - } - - public async Task ChangePassword(User user, string newPassword) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - if (newPassword != null) - { - newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); - } - - if (string.IsNullOrWhiteSpace(newPasswordHash)) - { - throw new ArgumentNullException(nameof(newPasswordHash)); - } - - user.EasyPassword = newPasswordHash; - - UpdateUser(user); - - UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); - } - - /// - /// Instantiates the new user. - /// - /// The name. - /// User. - private static User InstantiateNewUser(string name) - { - return new User - { - Name = name, - Id = Guid.NewGuid(), - DateCreated = DateTime.UtcNow, - DateModified = DateTime.UtcNow, - UsesIdForConfigurationPath = true, - //Salt = BCrypt.GenerateSalt() - }; - } - - private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); - - private string _lastPin; - private PasswordPinCreationResult _lastPasswordPinCreationResult; - private int _pinAttempts; - - private async Task CreatePasswordResetPin() - { - var num = new Random().Next(1, 9999); - - var path = PasswordResetFile; - - var pin = num.ToString("0000", CultureInfo.InvariantCulture); - _lastPin = pin; - - var time = TimeSpan.FromMinutes(5); - var expiration = DateTime.UtcNow.Add(time); - - var text = new StringBuilder(); - - var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; - - text.AppendLine("Use your web browser to visit:"); - text.AppendLine(string.Empty); - text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); - text.AppendLine(string.Empty); - text.AppendLine("Enter the following pin code:"); - text.AppendLine(string.Empty); - text.AppendLine(pin); - text.AppendLine(string.Empty); - - var localExpirationTime = expiration.ToLocalTime(); - // Tuesday, 22 August 2006 06:30 AM - text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); - - File.WriteAllText(path, text.ToString(), Encoding.UTF8); - - var result = new PasswordPinCreationResult - { - PinFile = path, - ExpirationDate = expiration - }; - - _lastPasswordPinCreationResult = result; - _pinAttempts = 0; - - return result; - } - - public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) - { - DeletePinFile(); - - var user = string.IsNullOrWhiteSpace(enteredUsername) ? - null : - GetUserByName(enteredUsername); - - var action = ForgotPasswordAction.InNetworkRequired; - string pinFile = null; - DateTime? expirationDate = null; - - if (user != null && !user.Policy.IsAdministrator) - { - action = ForgotPasswordAction.ContactAdmin; - } - else - { - if (isInNetwork) - { - action = ForgotPasswordAction.PinCode; - } - - var result = await CreatePasswordResetPin().ConfigureAwait(false); - pinFile = result.PinFile; - expirationDate = result.ExpirationDate; - } - - return new ForgotPasswordResult - { - Action = action, - PinFile = pinFile, - PinExpirationDate = expirationDate - }; - } - - public async Task RedeemPasswordResetPin(string pin) - { - DeletePinFile(); - - var usersReset = new List(); - - var valid = !string.IsNullOrWhiteSpace(_lastPin) && - string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && - _lastPasswordPinCreationResult != null && - _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; - - if (valid) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - - foreach (var user in Users) - { - await ResetPassword(user).ConfigureAwait(false); - - if (user.Policy.IsDisabled) - { - user.Policy.IsDisabled = false; - UpdateUserPolicy(user, user.Policy, true); - } - usersReset.Add(user.Name); - } - } - else - { - _pinAttempts++; - if (_pinAttempts >= 3) - { - _lastPin = null; - _lastPasswordPinCreationResult = null; - } - } - - return new PinRedeemResult - { - Success = valid, - UsersReset = usersReset.ToArray() - }; - } - - private void DeletePinFile() - { - try - { - _fileSystem.DeleteFile(PasswordResetFile); - } - catch - { - - } - } - - class PasswordPinCreationResult - { - public string PinFile { get; set; } - public DateTime ExpirationDate { get; set; } - } - - public UserPolicy GetUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - if (!File.Exists(path)) - { - return GetDefaultPolicy(user); - } - - try - { - lock (_policySyncLock) - { - return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); - } - } - catch (IOException) - { - return GetDefaultPolicy(user); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return GetDefaultPolicy(user); - } - } - - private static UserPolicy GetDefaultPolicy(User user) - { - return new UserPolicy - { - EnableContentDownloading = true, - EnableSyncTranscoding = true - }; - } - - private readonly object _policySyncLock = new object(); - public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) - { - var user = GetUserById(userId); - UpdateUserPolicy(user, userPolicy, true); - } - - private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) - { - // The xml serializer will output differently if the type is not exact - if (userPolicy.GetType() != typeof(UserPolicy)) - { - var json = _jsonSerializer.SerializeToString(userPolicy); - userPolicy = _jsonSerializer.DeserializeFromString(json); - } - - var path = GetPolicyFilePath(user); - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_policySyncLock) - { - _xmlSerializer.SerializeToFile(userPolicy, path); - user.Policy = userPolicy; - } - - if (fireEvent) - { - UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - - private void DeleteUserPolicy(User user) - { - var path = GetPolicyFilePath(user); - - try - { - lock (_policySyncLock) - { - _fileSystem.DeleteFile(path); - } - } - catch (IOException) - { - - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting policy file"); - } - } - - private static string GetPolicyFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); - } - - private static string GetConfigurationFilePath(User user) - { - return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); - } - - public UserConfiguration GetUserConfiguration(User user) - { - var path = GetConfigurationFilePath(user); - - if (!File.Exists(path)) - { - return new UserConfiguration(); - } - - try - { - lock (_configSyncLock) - { - return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); - } - } - catch (IOException) - { - return new UserConfiguration(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reading policy file: {path}", path); - - return new UserConfiguration(); - } - } - - private readonly object _configSyncLock = new object(); - public void UpdateConfiguration(Guid userId, UserConfiguration config) - { - var user = GetUserById(userId); - UpdateConfiguration(user, config); - } - - public void UpdateConfiguration(User user, UserConfiguration config) - { - UpdateConfiguration(user, config, true); - } - - private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) - { - var path = GetConfigurationFilePath(user); - - // The xml serializer will output differently if the type is not exact - if (config.GetType() != typeof(UserConfiguration)) - { - var json = _jsonSerializer.SerializeToString(config); - config = _jsonSerializer.DeserializeFromString(json); - } - - Directory.CreateDirectory(Path.GetDirectoryName(path)); - - lock (_configSyncLock) - { - _xmlSerializer.SerializeToFile(config, path); - user.Configuration = config; - } - - if (fireEvent) - { - UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); - } - } - } - - public class DeviceAccessEntryPoint : IServerEntryPoint - { - private IUserManager _userManager; - private IAuthenticationRepository _authRepo; - private IDeviceManager _deviceManager; - private ISessionManager _sessionManager; - - public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) - { - _userManager = userManager; - _authRepo = authRepo; - _deviceManager = deviceManager; - _sessionManager = sessionManager; - } - - public Task RunAsync() - { - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - - return Task.CompletedTask; - } - - private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) - { - var user = e.Argument; - if (!user.Policy.EnableAllDevices) - { - UpdateDeviceAccess(user); - } - } - - private void UpdateDeviceAccess(User user) - { - var existing = _authRepo.Get(new AuthenticationInfoQuery - { - UserId = user.Id - - }).Items; - - foreach (var authInfo in existing) - { - if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) - { - _sessionManager.Logout(authInfo); - } - } - } - - public void Dispose() - { - - } - } -} + } + + public string MakeValidUsername(string username) + { + if (IsValidUsername(username)) + { + return username; + } + + // Usernames can contain letters (a-z), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) + var builder = new StringBuilder(); + + foreach (var c in username) + { + if (IsValidUsernameCharacter(c)) + { + builder.Append(c); + } + } + return builder.ToString(); + } + + public async Task AuthenticateUser(string username, string password, string hashedPassword, string remoteEndPoint, bool isUserSession) + { + if (string.IsNullOrWhiteSpace(username)) + { + throw new ArgumentNullException(nameof(username)); + } + + var user = Users + .FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase)); + + var success = false; + IAuthenticationProvider authenticationProvider = null; + + if (user != null) + { + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, user, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + } + else + { + // user is null + var authResult = await AuthenticateLocalUser(username, password, hashedPassword, null, remoteEndPoint).ConfigureAwait(false); + authenticationProvider = authResult.Item1; + success = authResult.Item2; + + if (success && authenticationProvider != null && !(authenticationProvider is DefaultAuthenticationProvider)) + { + user = await CreateUser(username).ConfigureAwait(false); + + var hasNewUserPolicy = authenticationProvider as IHasNewUserPolicy; + if (hasNewUserPolicy != null) + { + var policy = hasNewUserPolicy.GetNewUserPolicy(); + UpdateUserPolicy(user, policy, true); + } + } + } + + if (success && user != null && authenticationProvider != null) + { + var providerId = GetAuthenticationProviderId(authenticationProvider); + + if (!string.Equals(providerId, user.Policy.AuthenticationProviderId, StringComparison.OrdinalIgnoreCase)) + { + user.Policy.AuthenticationProviderId = providerId; + UpdateUserPolicy(user, user.Policy, true); + } + } + + if (user == null) + { + throw new SecurityException("Invalid username or password entered."); + } + + if (user.Policy.IsDisabled) + { + throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name)); + } + + if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint)) + { + throw new SecurityException("Forbidden."); + } + + if (!user.IsParentalScheduleAllowed()) + { + throw new SecurityException("User is not allowed access at this time."); + } + + // Update LastActivityDate and LastLoginDate, then save + if (success) + { + if (isUserSession) + { + user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow; + UpdateUser(user); + } + UpdateInvalidLoginAttemptCount(user, 0); + } + else + { + UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1); + } + + _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied"); + + return success ? user : null; + } + + private static string GetAuthenticationProviderId(IAuthenticationProvider provider) + { + return provider.GetType().FullName; + } + + private IAuthenticationProvider GetAuthenticationProvider(User user) + { + return GetAuthenticationProviders(user).First(); + } + + private IAuthenticationProvider[] GetAuthenticationProviders(User user) + { + var authenticationProviderId = user == null ? null : user.Policy.AuthenticationProviderId; + + var providers = _authenticationProviders.Where(i => i.IsEnabled).ToArray(); + + if (!string.IsNullOrEmpty(authenticationProviderId)) + { + providers = providers.Where(i => string.Equals(authenticationProviderId, GetAuthenticationProviderId(i), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + + if (providers.Length == 0) + { + providers = new IAuthenticationProvider[] { _defaultAuthenticationProvider }; + } + + return providers; + } + + private async Task AuthenticateWithProvider(IAuthenticationProvider provider, string username, string password, User resolvedUser) + { + try + { + var requiresResolvedUser = provider as IRequiresResolvedUser; + if (requiresResolvedUser != null) + { + await requiresResolvedUser.Authenticate(username, password, resolvedUser).ConfigureAwait(false); + } + else + { + await provider.Authenticate(username, password).ConfigureAwait(false); + } + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error authenticating with provider {provider}", provider.Name); + + return false; + } + } + + private async Task> AuthenticateLocalUser(string username, string password, string hashedPassword, User user, string remoteEndPoint) + { + bool success = false; + IAuthenticationProvider authenticationProvider = null; + + if (password != null && user != null) + { + // Doesn't look like this is even possible to be used, because of password == null checks below + hashedPassword = _defaultAuthenticationProvider.GetHashedString(user, password); + } + + if (password == null) + { + // legacy + success = string.Equals(_defaultAuthenticationProvider.GetPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + foreach (var provider in GetAuthenticationProviders(user)) + { + success = await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false); + + if (success) + { + authenticationProvider = provider; + break; + } + } + } + + if (user != null) + { + if (!success && _networkManager.IsInLocalNetwork(remoteEndPoint) && user.Configuration.EnableLocalPassword) + { + if (password == null) + { + // legacy + success = string.Equals(GetLocalPasswordHash(user), hashedPassword.Replace("-", string.Empty), StringComparison.OrdinalIgnoreCase); + } + else + { + success = string.Equals(GetLocalPasswordHash(user), _defaultAuthenticationProvider.GetHashedString(user, password), StringComparison.OrdinalIgnoreCase); + } + } + } + + return new Tuple(authenticationProvider, success); + } + + private void UpdateInvalidLoginAttemptCount(User user, int newValue) + { + if (user.Policy.InvalidLoginAttemptCount == newValue || newValue <= 0) + { + return; + } + + user.Policy.InvalidLoginAttemptCount = newValue; + + var maxCount = user.Policy.IsAdministrator ? 3 : 5; + + var fireLockout = false; + + if (newValue >= maxCount) + { + _logger.LogDebug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue); + user.Policy.IsDisabled = true; + + fireLockout = true; + } + + UpdateUserPolicy(user, user.Policy, false); + + if (fireLockout) + { + UserLockedOut?.Invoke(this, new GenericEventArgs(user)); + } + } + + private string GetLocalPasswordHash(User user) + { + return string.IsNullOrEmpty(user.EasyPassword) + ? _defaultAuthenticationProvider.GetEmptyHashedString(user) + : user.EasyPassword; + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.Equals(passwordHash, _defaultAuthenticationProvider.GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + } + + /// + /// Loads the users from the repository + /// + /// IEnumerable{User}. + private User[] LoadUsers() + { + var users = UserRepository.RetrieveAllUsers(); + + // There always has to be at least one user. + if (users.Count == 0) + { + var defaultName = Environment.UserName; + if (string.IsNullOrWhiteSpace(defaultName)) + { + defaultName = "MyJellyfinUser"; + } + var name = MakeValidUsername(defaultName); + + var user = InstantiateNewUser(name); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + users.Add(user); + + user.Policy.IsAdministrator = true; + user.Policy.EnableContentDeletion = true; + user.Policy.EnableRemoteControlOfOtherUsers = true; + UpdateUserPolicy(user, user.Policy, false); + } + + return users.ToArray(); + } + + public UserDto GetUserDto(User user, string remoteEndPoint = null) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var hasConfiguredPassword = GetAuthenticationProvider(user).HasPassword(user).Result; + var hasConfiguredEasyPassword = !IsPasswordEmpty(user, GetLocalPasswordHash(user)); + + var hasPassword = user.Configuration.EnableLocalPassword && !string.IsNullOrEmpty(remoteEndPoint) && _networkManager.IsInLocalNetwork(remoteEndPoint) ? + hasConfiguredEasyPassword : + hasConfiguredPassword; + + var dto = new UserDto + { + Id = user.Id, + Name = user.Name, + HasPassword = hasPassword, + HasConfiguredPassword = hasConfiguredPassword, + HasConfiguredEasyPassword = hasConfiguredEasyPassword, + LastActivityDate = user.LastActivityDate, + LastLoginDate = user.LastLoginDate, + Configuration = user.Configuration, + ServerId = _appHost.SystemId, + Policy = user.Policy + }; + + if (!hasPassword && Users.Count() == 1) + { + dto.EnableAutoLogin = true; + } + + var image = user.GetImageInfo(ImageType.Primary, 0); + + if (image != null) + { + dto.PrimaryImageTag = GetImageCacheTag(user, image); + + try + { + _dtoServiceFactory().AttachPrimaryImageAspectRatio(dto, user); + } + catch (Exception ex) + { + // Have to use a catch-all unfortunately because some .net image methods throw plain Exceptions + _logger.LogError(ex, "Error generating PrimaryImageAspectRatio for {user}", user.Name); + } + } + + return dto; + } + + public UserDto GetOfflineUserDto(User user) + { + var dto = GetUserDto(user); + + dto.ServerName = _appHost.FriendlyName; + + return dto; + } + + private string GetImageCacheTag(BaseItem item, ItemImageInfo image) + { + try + { + return _imageProcessorFactory().GetImageCacheTag(item, image); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting {imageType} image info for {imagePath}", image.Type, image.Path); + return null; + } + } + + /// + /// Refreshes metadata for each user + /// + /// The cancellation token. + /// Task. + public async Task RefreshUsersMetadata(CancellationToken cancellationToken) + { + foreach (var user in Users) + { + await user.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), cancellationToken).ConfigureAwait(false); + } + } + + /// + /// Renames the user. + /// + /// The user. + /// The new name. + /// Task. + /// user + /// + public async Task RenameUser(User user, string newName) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (string.IsNullOrEmpty(newName)) + { + throw new ArgumentNullException(nameof(newName)); + } + + if (Users.Any(u => u.Id != user.Id && u.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))) + { + throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", newName)); + } + + if (user.Name.Equals(newName, StringComparison.Ordinal)) + { + throw new ArgumentException("The new and old names must be different."); + } + + await user.Rename(newName); + + OnUserUpdated(user); + } + + /// + /// Updates the user. + /// + /// The user. + /// user + /// + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.Id.Equals(Guid.Empty) || !Users.Any(u => u.Id.Equals(user.Id))) + { + throw new ArgumentException(string.Format("User with name '{0}' and Id {1} does not exist.", user.Name, user.Id)); + } + + user.DateModified = DateTime.UtcNow; + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.UpdateUser(user); + + OnUserUpdated(user); + } + + public event EventHandler> UserCreated; + + private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); + + /// + /// Creates the user. + /// + /// The name. + /// User. + /// name + /// + public async Task CreateUser(string name) + { + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!IsValidUsername(name)) + { + 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))) + { + throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); + } + + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + try + { + var user = InstantiateNewUser(name); + + var list = Users.ToList(); + list.Add(user); + _users = list.ToArray(); + + user.DateLastSaved = DateTime.UtcNow; + + UserRepository.CreateUser(user); + + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + + return user; + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + /// + public async Task DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var allUsers = Users.ToList(); + + if (allUsers.FirstOrDefault(u => u.Id == user.Id) == null) + { + throw new ArgumentException(string.Format("The user cannot be deleted because there is no user with the Name {0} and Id {1}.", user.Name, user.Id)); + } + + if (allUsers.Count == 1) + { + throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name)); + } + + if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1) + { + throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name)); + } + + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + try + { + var configPath = GetConfigurationFilePath(user); + + UserRepository.DeleteUser(user); + + try + { + _fileSystem.DeleteFile(configPath); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting file {path}", configPath); + } + + DeleteUserPolicy(user); + + _users = allUsers.Where(i => i.Id != user.Id).ToArray(); + + OnUserDeleted(user); + } + finally + { + _userListLock.Release(); + } + } + + /// + /// Resets the password by clearing it. + /// + /// Task. + public Task ResetPassword(User user) + { + return ChangePassword(user, string.Empty); + } + + public void ResetEasyPassword(User user) + { + ChangeEasyPassword(user, string.Empty, null); + } + + public async Task ChangePassword(User user, string newPassword) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + await GetAuthenticationProvider(user).ChangePassword(user, newPassword).ConfigureAwait(false); + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (newPassword != null) + { + newPasswordHash = _defaultAuthenticationProvider.GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(newPasswordHash)) + { + throw new ArgumentNullException(nameof(newPasswordHash)); + } + + user.EasyPassword = newPasswordHash; + + UpdateUser(user); + + UserPasswordChanged?.Invoke(this, new GenericEventArgs(user)); + } + + /// + /// Instantiates the new user. + /// + /// The name. + /// User. + private static User InstantiateNewUser(string name) + { + return new User + { + Name = name, + Id = Guid.NewGuid(), + DateCreated = DateTime.UtcNow, + DateModified = DateTime.UtcNow, + UsesIdForConfigurationPath = true, + //Salt = BCrypt.GenerateSalt() + }; + } + + private string PasswordResetFile => Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); + + private string _lastPin; + private PasswordPinCreationResult _lastPasswordPinCreationResult; + private int _pinAttempts; + + private async Task CreatePasswordResetPin() + { + var num = new Random().Next(1, 9999); + + var path = PasswordResetFile; + + var pin = num.ToString("0000", CultureInfo.InvariantCulture); + _lastPin = pin; + + var time = TimeSpan.FromMinutes(5); + var expiration = DateTime.UtcNow.Add(time); + + var text = new StringBuilder(); + + var localAddress = (await _appHost.GetLocalApiUrl(CancellationToken.None).ConfigureAwait(false)) ?? string.Empty; + + text.AppendLine("Use your web browser to visit:"); + text.AppendLine(string.Empty); + text.AppendLine(localAddress + "/web/index.html#!/forgotpasswordpin.html"); + text.AppendLine(string.Empty); + text.AppendLine("Enter the following pin code:"); + text.AppendLine(string.Empty); + text.AppendLine(pin); + text.AppendLine(string.Empty); + + var localExpirationTime = expiration.ToLocalTime(); + // Tuesday, 22 August 2006 06:30 AM + text.AppendLine("The pin code will expire at " + localExpirationTime.ToString("f1", CultureInfo.CurrentCulture)); + + File.WriteAllText(path, text.ToString(), Encoding.UTF8); + + var result = new PasswordPinCreationResult + { + PinFile = path, + ExpirationDate = expiration + }; + + _lastPasswordPinCreationResult = result; + _pinAttempts = 0; + + return result; + } + + public async Task StartForgotPasswordProcess(string enteredUsername, bool isInNetwork) + { + DeletePinFile(); + + var user = string.IsNullOrWhiteSpace(enteredUsername) ? + null : + GetUserByName(enteredUsername); + + var action = ForgotPasswordAction.InNetworkRequired; + string pinFile = null; + DateTime? expirationDate = null; + + if (user != null && !user.Policy.IsAdministrator) + { + action = ForgotPasswordAction.ContactAdmin; + } + else + { + if (isInNetwork) + { + action = ForgotPasswordAction.PinCode; + } + + var result = await CreatePasswordResetPin().ConfigureAwait(false); + pinFile = result.PinFile; + expirationDate = result.ExpirationDate; + } + + return new ForgotPasswordResult + { + Action = action, + PinFile = pinFile, + PinExpirationDate = expirationDate + }; + } + + public async Task RedeemPasswordResetPin(string pin) + { + DeletePinFile(); + + var usersReset = new List(); + + var valid = !string.IsNullOrWhiteSpace(_lastPin) && + string.Equals(_lastPin, pin, StringComparison.OrdinalIgnoreCase) && + _lastPasswordPinCreationResult != null && + _lastPasswordPinCreationResult.ExpirationDate > DateTime.UtcNow; + + if (valid) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + + foreach (var user in Users) + { + await ResetPassword(user).ConfigureAwait(false); + + if (user.Policy.IsDisabled) + { + user.Policy.IsDisabled = false; + UpdateUserPolicy(user, user.Policy, true); + } + usersReset.Add(user.Name); + } + } + else + { + _pinAttempts++; + if (_pinAttempts >= 3) + { + _lastPin = null; + _lastPasswordPinCreationResult = null; + } + } + + return new PinRedeemResult + { + Success = valid, + UsersReset = usersReset.ToArray() + }; + } + + private void DeletePinFile() + { + try + { + _fileSystem.DeleteFile(PasswordResetFile); + } + catch + { + + } + } + + class PasswordPinCreationResult + { + public string PinFile { get; set; } + public DateTime ExpirationDate { get; set; } + } + + public UserPolicy GetUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + if (!File.Exists(path)) + { + return GetDefaultPolicy(user); + } + + try + { + lock (_policySyncLock) + { + return (UserPolicy)_xmlSerializer.DeserializeFromFile(typeof(UserPolicy), path); + } + } + catch (IOException) + { + return GetDefaultPolicy(user); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return GetDefaultPolicy(user); + } + } + + private static UserPolicy GetDefaultPolicy(User user) + { + return new UserPolicy + { + EnableContentDownloading = true, + EnableSyncTranscoding = true + }; + } + + private readonly object _policySyncLock = new object(); + public void UpdateUserPolicy(Guid userId, UserPolicy userPolicy) + { + var user = GetUserById(userId); + UpdateUserPolicy(user, userPolicy, true); + } + + private void UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent) + { + // The xml serializer will output differently if the type is not exact + if (userPolicy.GetType() != typeof(UserPolicy)) + { + var json = _jsonSerializer.SerializeToString(userPolicy); + userPolicy = _jsonSerializer.DeserializeFromString(json); + } + + var path = GetPolicyFilePath(user); + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_policySyncLock) + { + _xmlSerializer.SerializeToFile(userPolicy, path); + user.Policy = userPolicy; + } + + if (fireEvent) + { + UserPolicyUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + + private void DeleteUserPolicy(User user) + { + var path = GetPolicyFilePath(user); + + try + { + lock (_policySyncLock) + { + _fileSystem.DeleteFile(path); + } + } + catch (IOException) + { + + } + catch (Exception ex) + { + _logger.LogError(ex, "Error deleting policy file"); + } + } + + private static string GetPolicyFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml"); + } + + private static string GetConfigurationFilePath(User user) + { + return Path.Combine(user.ConfigurationDirectoryPath, "config.xml"); + } + + public UserConfiguration GetUserConfiguration(User user) + { + var path = GetConfigurationFilePath(user); + + if (!File.Exists(path)) + { + return new UserConfiguration(); + } + + try + { + lock (_configSyncLock) + { + return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path); + } + } + catch (IOException) + { + return new UserConfiguration(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading policy file: {path}", path); + + return new UserConfiguration(); + } + } + + private readonly object _configSyncLock = new object(); + public void UpdateConfiguration(Guid userId, UserConfiguration config) + { + var user = GetUserById(userId); + UpdateConfiguration(user, config); + } + + public void UpdateConfiguration(User user, UserConfiguration config) + { + UpdateConfiguration(user, config, true); + } + + private void UpdateConfiguration(User user, UserConfiguration config, bool fireEvent) + { + var path = GetConfigurationFilePath(user); + + // The xml serializer will output differently if the type is not exact + if (config.GetType() != typeof(UserConfiguration)) + { + var json = _jsonSerializer.SerializeToString(config); + config = _jsonSerializer.DeserializeFromString(json); + } + + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + lock (_configSyncLock) + { + _xmlSerializer.SerializeToFile(config, path); + user.Configuration = config; + } + + if (fireEvent) + { + UserConfigurationUpdated?.Invoke(this, new GenericEventArgs { Argument = user }); + } + } + } + + public class DeviceAccessEntryPoint : IServerEntryPoint + { + private IUserManager _userManager; + private IAuthenticationRepository _authRepo; + private IDeviceManager _deviceManager; + private ISessionManager _sessionManager; + + public DeviceAccessEntryPoint(IUserManager userManager, IAuthenticationRepository authRepo, IDeviceManager deviceManager, ISessionManager sessionManager) + { + _userManager = userManager; + _authRepo = authRepo; + _deviceManager = deviceManager; + _sessionManager = sessionManager; + } + + public Task RunAsync() + { + _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; + + return Task.CompletedTask; + } + + private void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + { + var user = e.Argument; + if (!user.Policy.EnableAllDevices) + { + UpdateDeviceAccess(user); + } + } + + private void UpdateDeviceAccess(User user) + { + var existing = _authRepo.Get(new AuthenticationInfoQuery + { + UserId = user.Id + + }).Items; + + foreach (var authInfo in existing) + { + if (!string.IsNullOrEmpty(authInfo.DeviceId) && !_deviceManager.CanAccessDevice(user, authInfo.DeviceId)) + { + _sessionManager.Logout(authInfo); + } + } + } + + public void Dispose() + { + + } + } +} diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index ec7e57fec5..8accc696e3 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,15 +1,15 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptoProvider - { - Guid GetMD5(string str); - byte[] ComputeMD5(Stream str); - byte[] ComputeMD5(byte[] bytes); - byte[] ComputeSHA1(byte[] bytes); +using System; +using System.IO; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); IEnumerable GetSupportedHashMethods(); byte[] ComputeHash(string HashMethod, byte[] bytes); byte[] ComputeHashWithDefaultMethod(byte[] bytes); @@ -17,5 +17,6 @@ namespace MediaBrowser.Model.Cryptography byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - } -} + string DefaultHashMethod { get; } + } +} diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index d37220ab2f..524484b107 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -33,15 +33,15 @@ namespace MediaBrowser.Model.Cryptography if (a.Length == 4) { Salt = a[2]; - SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + SaltBytes = FromByteString(Salt); Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } else { Salt = string.Empty; Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } } else @@ -49,15 +49,15 @@ namespace MediaBrowser.Model.Cryptography if (a.Length == 4) { Salt = a[2]; - SaltBytes = Convert.FromBase64CharArray(Salt.ToCharArray(), 0, Salt.Length); + SaltBytes = FromByteString(Salt); Hash = a[3]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } else { Salt = string.Empty; Hash = a[2]; - HashBytes = Convert.FromBase64CharArray(Hash.ToCharArray(), 0, Hash.Length); + HashBytes = FromByteString(Hash); } } @@ -68,7 +68,17 @@ namespace MediaBrowser.Model.Cryptography { Id = "SHA256"; SaltBytes = cryptoProvider2.GenerateSalt(); - Salt = Convert.ToBase64String(SaltBytes); + Salt = BitConverter.ToString(SaltBytes).Replace("-", ""); + } + + private byte[] FromByteString(string ByteString) + { + List Bytes = new List(); + for (int i = 0; i < ByteString.Length; i += 2) + { + Bytes.Add(Convert.ToByte(ByteString.Substring(i, 2),16)); + } + return Bytes.ToArray(); } private string SerializeParameters() { @@ -77,7 +87,7 @@ namespace MediaBrowser.Model.Cryptography { ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); } - if (ReturnString[0] == ',') + if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); } @@ -85,8 +95,15 @@ namespace MediaBrowser.Model.Cryptography } public override string ToString() - { - return String.Format("${0}${1}${2}${3}", Id, SerializeParameters(), Salt, Hash); + { + string OutString = "$"; + OutString += Id; + if (!string.IsNullOrEmpty(SerializeParameters())) + OutString += $"${SerializeParameters()}"; + if (!string.IsNullOrEmpty(Salt)) + OutString += $"${Salt}"; + OutString += $"${Hash}"; + return OutString; } } From 1dc5a624a7b735fe872110a2a56529560381abf0 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 22:24:05 -0800 Subject: [PATCH 004/166] fixed gitignore fail --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3bea16e5d2..e48ab1e798 100644 --- a/.gitignore +++ b/.gitignore @@ -263,5 +263,4 @@ deployment/**/pkg-dist/ deployment/**/pkg-dist-tmp/ deployment/collect-dist/ -jellyfin_version.ini -MediaBrowser.WebDashboard/jellyfin-web +jellyfin_version.ini \ No newline at end of file From 1ffd443d5aaec408170eaec31923a1cbbe1bb929 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 12 Feb 2019 22:30:26 -0800 Subject: [PATCH 005/166] fixed nul user check to be first per justaman --- .../Library/DefaultAuthenticationProvider.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 92346c65aa..255fd82527 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -32,15 +32,16 @@ namespace Emby.Server.Implementations.Library //This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) - { - ConvertPasswordFormat(resolvedUser); - byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - bool success = false; + { + bool success = false; if (resolvedUser == null) { success = false; throw new Exception("Invalid username or password"); } + ConvertPasswordFormat(resolvedUser); + byte[] passwordbytes = Encoding.UTF8.GetBytes(password); + if (!resolvedUser.Password.Contains("$")) { ConvertPasswordFormat(resolvedUser); From 77602aff889e605f8178ecf95592c0d75102e59f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 13 Feb 2019 00:33:00 -0800 Subject: [PATCH 006/166] Minor fixes re:PR870, added null checks from PR876 --- .../Cryptography/CryptographyProvider.cs | 38 ++++++---- .../Data/SqliteUserRepository.cs | 32 ++++++++ .../Library/DefaultAuthenticationProvider.cs | 71 ++++++------------ .../Library/UserManager.cs | 6 +- .../Cryptography/PasswordHash.cs | 75 +++++++++++-------- 5 files changed, 124 insertions(+), 98 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 4f2bc1b030..7817989e7f 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -9,7 +9,7 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private List SupportedHashMethods = new List(); + private HashSet SupportedHashMethods; public string DefaultHashMethod => "SHA256"; private RandomNumberGenerator rng; private int defaultiterations = 1000; @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.Cryptography { //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 - SupportedHashMethods = new List + SupportedHashMethods = new HashSet() { "MD5" ,"System.Security.Cryptography.MD5" @@ -71,9 +71,9 @@ namespace Emby.Server.Implementations.Cryptography return SupportedHashMethods; } - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt) - { - using (var r = new Rfc2898DeriveBytes(bytes, salt, defaultiterations, new HashAlgorithmName(method))) + private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) + { + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) { return r.GetBytes(32); } @@ -102,30 +102,40 @@ namespace Emby.Server.Implementations.Cryptography } else { - return PBKDF2(HashMethod, bytes, salt); + return PBKDF2(HashMethod, bytes, salt,defaultiterations); } } else { throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); } - } + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { - return PBKDF2(DefaultHashMethod, bytes, salt); + return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); } public byte[] ComputeHash(PasswordHash hash) - { - return ComputeHash(hash.Id, hash.HashBytes, hash.SaltBytes); - } - + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString()); + } + else + { + try { iterations = int.Parse(hash.Parameters["iterations"]); } + catch (Exception e) { iterations = defaultiterations; throw new Exception($"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[8]; + byte[] salt = new byte[64]; rng.GetBytes(salt); return salt; - } + } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index db359d7ddc..b3d4573427 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -55,6 +55,7 @@ namespace Emby.Server.Implementations.Data { TryMigrateToLocalUsersTable(connection); } + RemoveEmptyPasswordHashes(); } } @@ -73,6 +74,37 @@ 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") || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")) + { + 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); + } + } + + } + /// /// Save a user in the repo /// diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 255fd82527..ca6217016b 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -36,32 +36,27 @@ namespace Emby.Server.Implementations.Library bool success = false; if (resolvedUser == null) { - success = false; throw new Exception("Invalid username or password"); } ConvertPasswordFormat(resolvedUser); byte[] passwordbytes = Encoding.UTF8.GetBytes(password); - - if (!resolvedUser.Password.Contains("$")) - { - ConvertPasswordFormat(resolvedUser); - } - PasswordHash ReadyHash = new PasswordHash(resolvedUser.Password); + + PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] CalculatedHash; string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == ReadyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) { - if (String.IsNullOrEmpty(ReadyHash.Salt)) + if (String.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(ReadyHash.Id, passwordbytes, ReadyHash.SaltBytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } - if (CalculatedHashString == ReadyHash.Hash) + if (CalculatedHashString == readyHash.Hash) { success = true; //throw new Exception("Invalid username or password"); @@ -69,8 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - success = false; - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", ReadyHash.Id)); + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -105,26 +99,6 @@ namespace Emby.Server.Implementations.Library } } - // OLD VERSION //public Task Authenticate(string username, string password, User resolvedUser) - // OLD VERSION //{ - // OLD VERSION // if (resolvedUser == null) - // OLD VERSION // { - // OLD VERSION // throw new Exception("Invalid username or password"); - // OLD VERSION // } - // OLD VERSION // - // OLD VERSION // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); - // OLD VERSION // - // OLD VERSION // if (!success) - // OLD VERSION // { - // OLD VERSION // throw new Exception("Invalid username or password"); - // OLD VERSION // } - // OLD VERSION // - // OLD VERSION // return Task.FromResult(new ProviderAuthenticationResult - // OLD VERSION // { - // OLD VERSION // Username = username - // OLD VERSION // }); - // OLD VERSION //} - public Task HasPassword(User user) { var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); @@ -133,7 +107,7 @@ namespace Emby.Server.Implementations.Library private bool IsPasswordEmpty(User user, string passwordHash) { - return string.Equals(passwordHash, GetEmptyHashedString(user), StringComparison.OrdinalIgnoreCase); + return string.IsNullOrEmpty(passwordHash); } public Task ChangePassword(User user, string newPassword) @@ -144,7 +118,7 @@ namespace Emby.Server.Implementations.Library if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); - passwordHash.Salt = BitConverter.ToString(passwordHash.SaltBytes).Replace("-",""); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); }else if (newPassword != null) @@ -164,19 +138,18 @@ namespace Emby.Server.Implementations.Library public string GetPasswordHash(User user) { - return string.IsNullOrEmpty(user.Password) - ? GetEmptyHashedString(user) - : user.Password; + return user.Password; } public string GetEmptyHashedString(User user) { - return GetHashedString(user, string.Empty); + return null; } - public string GetHashedStringChangeAuth(string NewPassword, PasswordHash passwordHash) + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(NewPassword), passwordHash.SaltBytes)).Replace("-", string.Empty); + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); } /// @@ -184,8 +157,6 @@ namespace Emby.Server.Implementations.Library /// public string GetHashedString(User user, string str) { - //This is legacy. Deprecated in the auth method. - //return BitConverter.ToString(_cryptoProvider2.ComputeSHA1(Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); PasswordHash passwordHash; if (String.IsNullOrEmpty(user.Password)) { @@ -197,13 +168,15 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(user.Password); } if (passwordHash.SaltBytes != null) - { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str), passwordHash.SaltBytes)).Replace("-",string.Empty); + { + //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 - { - return BitConverter.ToString(_cryptographyProvider.ComputeHash(passwordHash.Id, Encoding.UTF8.GetBytes(str))).Replace("-", string.Empty); - //throw new Exception("User does not have a hash, this should not be possible"); + { + //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/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index a139c4e736..b8777a4802 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -217,9 +217,8 @@ namespace Emby.Server.Implementations.Library } } - public bool IsValidUsername(string username) + public static bool IsValidUsername(string username) { - //The old way was dumb, we should make it less dumb, lets do so. //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 string UserNameRegex = "^[\\w-'._@]*$"; @@ -229,8 +228,7 @@ namespace Emby.Server.Implementations.Library private static bool IsValidUsernameCharacter(char i) { - string UserNameRegex = "^[\\w-'._@]*$"; - return Regex.IsMatch(i.ToString(), UserNameRegex); + return IsValidUsername(i.ToString()); } public string MakeValidUsername(string username) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 524484b107..cd61657c18 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -16,70 +16,78 @@ namespace MediaBrowser.Model.Cryptography public byte[] SaltBytes; public string Hash; public byte[] HashBytes; - public PasswordHash(string StorageString) + public PasswordHash(string storageString) { - string[] a = StorageString.Split('$'); - Id = a[1]; - if (a[2].Contains("=")) + string[] SplitStorageString = storageString.Split('$'); + Id = SplitStorageString[1]; + if (SplitStorageString[2].Contains("=")) { - foreach (string paramset in (a[2].Split(','))) + foreach (string paramset in (SplitStorageString[2].Split(','))) { if (!String.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - Parameters.Add(fields[0], fields[1]); + string[] fields = paramset.Split('='); + if(fields.Length == 2) + { + Parameters.Add(fields[0], fields[1]); + } } } - if (a.Length == 4) + if (SplitStorageString.Length == 5) { - Salt = a[2]; - SaltBytes = FromByteString(Salt); - Hash = a[3]; - HashBytes = FromByteString(Hash); + Salt = SplitStorageString[3]; + SaltBytes = ConvertFromByteString(Salt); + Hash = SplitStorageString[4]; + HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = a[3]; - HashBytes = FromByteString(Hash); + Hash = SplitStorageString[3]; + HashBytes = ConvertFromByteString(Hash); } } else { - if (a.Length == 4) + if (SplitStorageString.Length == 4) { - Salt = a[2]; - SaltBytes = FromByteString(Salt); - Hash = a[3]; - HashBytes = FromByteString(Hash); + Salt = SplitStorageString[2]; + SaltBytes = ConvertFromByteString(Salt); + Hash = SplitStorageString[3]; + HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = a[2]; - HashBytes = FromByteString(Hash); + Hash = SplitStorageString[2]; + HashBytes = ConvertFromByteString(Hash); } } } - public PasswordHash(ICryptoProvider cryptoProvider2) + public PasswordHash(ICryptoProvider cryptoProvider) { - Id = "SHA256"; - SaltBytes = cryptoProvider2.GenerateSalt(); - Salt = BitConverter.ToString(SaltBytes).Replace("-", ""); + Id = cryptoProvider.DefaultHashMethod; + SaltBytes = cryptoProvider.GenerateSalt(); + Salt = ConvertToByteString(SaltBytes); } - private byte[] FromByteString(string ByteString) + public static byte[] ConvertFromByteString(string byteString) { List Bytes = new List(); - for (int i = 0; i < ByteString.Length; i += 2) + for (int i = 0; i < byteString.Length; i += 2) { - Bytes.Add(Convert.ToByte(ByteString.Substring(i, 2),16)); + Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } return Bytes.ToArray(); - } + } + public static string ConvertToByteString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } + private string SerializeParameters() { string ReturnString = String.Empty; @@ -98,10 +106,15 @@ namespace MediaBrowser.Model.Cryptography { string OutString = "$"; OutString += Id; - if (!string.IsNullOrEmpty(SerializeParameters())) - OutString += $"${SerializeParameters()}"; + string paramstring = SerializeParameters(); + if (!string.IsNullOrEmpty(paramstring)) + { + OutString += $"${paramstring}"; + } if (!string.IsNullOrEmpty(Salt)) + { OutString += $"${Salt}"; + } OutString += $"${Hash}"; return OutString; } From 9e58e31de08b4d8e7922038a9f291e720a778b8f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 13 Feb 2019 00:43:48 -0800 Subject: [PATCH 007/166] Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs fix to styling Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultAuthenticationProvider.cs | 226 +++++++++--------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index ca6217016b..750807b10c 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,39 +1,39 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) - { - _cryptographyProvider = crypto; - } - - 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 Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - - //This is the verson that we need to use for local users. Because reasons. +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + 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 Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + + //This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) - { - bool success = false; + { + bool success = false; if (resolvedUser == null) { throw new Exception("Invalid username or password"); @@ -42,18 +42,18 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; + byte[] CalculatedHash; string CalculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) { if (String.IsNullOrEmpty(readyHash.Salt)) { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } else { - CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); } if (CalculatedHashString == readyHash.Hash) @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); + throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); } //var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); @@ -78,10 +78,10 @@ namespace Emby.Server.Implementations.Library { Username = username }); - } - - //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. + } + + //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)) @@ -90,71 +90,71 @@ namespace Emby.Server.Implementations.Library { 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 HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); - } - - public Task ChangePassword(User user, string newPassword) - { - //string newPasswordHash = null; - ConvertPasswordFormat(user); - PasswordHash passwordHash = new PasswordHash(user.Password); - if(passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) - { - 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(passwordHash.Hash)) - { - throw new ArgumentNullException(nameof(passwordHash.Hash)); - } - - user.Password = passwordHash.ToString(); - - return Task.CompletedTask; - } - - public string GetPasswordHash(User user) - { - return user.Password; - } - - public string GetEmptyHashedString(User user) - { - return null; - } - - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - - /// - /// Gets the hashed string. - /// + } + + public Task HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string passwordHash) + { + return string.IsNullOrEmpty(passwordHash); + } + + public Task ChangePassword(User user, string newPassword) + { + //string newPasswordHash = null; + ConvertPasswordFormat(user); + PasswordHash passwordHash = new PasswordHash(user.Password); + if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) + { + 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(passwordHash.Hash)) + { + throw new ArgumentNullException(nameof(passwordHash.Hash)); + } + + user.Password = passwordHash.ToString(); + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return user.Password; + } + + public string GetEmptyHashedString(User user) + { + return null; + } + + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + { + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + + /// + /// Gets the hashed string. + /// public string GetHashedString(User user, string str) { PasswordHash passwordHash; @@ -163,23 +163,23 @@ namespace Emby.Server.Implementations.Library passwordHash = new PasswordHash(_cryptographyProvider); } else - { + { ConvertPasswordFormat(user); passwordHash = new PasswordHash(user.Password); } if (passwordHash.SaltBytes != null) - { - //the password is modern format with PBKDF and we should take advantage of that - passwordHash.HashBytes = Encoding.UTF8.GetBytes(str); + { + //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))); - } - - - } - } -} + } + + + } + } +} From d8e6808d77eb70025b4a26538a1deb814cbe2831 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 13 Feb 2019 00:44:07 -0800 Subject: [PATCH 008/166] Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs fix to styling Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultAuthenticationProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 750807b10c..33428c05ee 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -91,6 +91,7 @@ namespace Emby.Server.Implementations.Library string hash = user.Password; user.Password = String.Format("$SHA1${0}", hash); } + if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; From bb503638123bafb42594456fa440e71b80088fb5 Mon Sep 17 00:00:00 2001 From: bobberb Date: Sun, 17 Feb 2019 00:21:55 +0000 Subject: [PATCH 009/166] Translated using Weblate (Hebrew) Currently translated at 97.8% (92 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/ --- Emby.Server.Implementations/Localization/Core/he.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json index fff1d1f0ec..0ed998c4bd 100644 --- a/Emby.Server.Implementations/Localization/Core/he.json +++ b/Emby.Server.Implementations/Localization/Core/he.json @@ -1,8 +1,8 @@ { - "Albums": "Albums", + "Albums": "אלבומים", "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", + "Application": "אפליקציה", + "Artists": "אמנים", "AuthenticationSucceededWithUserName": "{0} successfully authenticated", "Books": "ספרים", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", From 9f3aa2cead95ec0a66a518919c179eea4cad5d9c Mon Sep 17 00:00:00 2001 From: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> Date: Mon, 18 Feb 2019 00:31:03 -0800 Subject: [PATCH 010/166] Apply suggestions from code review Adding minor stylistic suggestions from Bond-009 Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Cryptography/CryptographyProvider.cs | 41 +- .../Data/SqliteUserRepository.cs | 469 +++++++++--------- .../Library/DefaultAuthenticationProvider.cs | 9 +- 3 files changed, 264 insertions(+), 255 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 7817989e7f..436443f066 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Cryptography } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) - { + { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) { return r.GetBytes(32); @@ -107,9 +107,9 @@ namespace Emby.Server.Implementations.Cryptography } else { - throw new CryptographicException(String.Format("Requested hash method is not supported: {0}", HashMethod)); + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}")); } - } + } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { @@ -117,25 +117,32 @@ namespace Emby.Server.Implementations.Cryptography } public byte[] ComputeHash(PasswordHash hash) - { - int iterations = defaultiterations; - if (!hash.Parameters.ContainsKey("iterations")) - { - hash.Parameters.Add("iterations", defaultiterations.ToString()); - } - else - { - try { iterations = int.Parse(hash.Parameters["iterations"]); } - catch (Exception e) { iterations = defaultiterations; throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", e); } + { + int iterations = defaultiterations; + if (!hash.Parameters.ContainsKey("iterations")) + { + hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); } - return PBKDF2(hash.Id, hash.HashBytes, hash.SaltBytes,iterations); - } - + else + { + try + { + iterations = int.Parse(hash.Parameters["iterations"]); + } + catch (Exception e) + { + iterations = defaultiterations; + throw new Exception($"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]; rng.GetBytes(salt); return salt; - } + } } } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index b3d4573427..1b6deae7d3 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,85 +1,86 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly IJsonSerializer _jsonSerializer; - - public SqliteUserRepository( - ILoggerFactory loggerFactory, - IServerApplicationPaths appPaths, - IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) - { - _jsonSerializer = jsonSerializer; - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); - } - RemoveEmptyPasswordHashes(); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - +using System; +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteUserRepository + /// + public class SqliteUserRepository : BaseSqliteRepository, IUserRepository + { + private readonly IJsonSerializer _jsonSerializer; + + public SqliteUserRepository( + ILoggerFactory loggerFactory, + IServerApplicationPaths appPaths, + IJsonSerializer jsonSerializer) + : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + { + _jsonSerializer = jsonSerializer; + + DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name => "SQLite"; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); + + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); + + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); + } + RemoveEmptyPasswordHashes(); + } + } + + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error migrating users database"); + } + } + 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") || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709")) + if (!string.Equals(user.Password, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal) + || !string.Equals(user.Password, "$SHA1$DA39A3EE5E6B4B0D3255BFEF95601890AFD80709", StringComparison.Ordinal)) { continue; } @@ -103,160 +104,160 @@ namespace Emby.Server.Implementations.Data } } - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, false); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - 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); - } - } - } - - private User GetUser(Guid guid, bool openLock) - { - using (openLock ? WriteLock.Read() : null) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - var list = new List(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } - } - } - - return list; - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - } -} + } + + /// + /// Save a user in the repo + /// + public void CreateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) + { + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + + }, TransactionMode); + } + } + } + + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + 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); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public List RetrieveAllUsers() + { + var list = new List(); + + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + list.Add(GetUser(row)); + } + } + } + + return list; + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) + { + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 33428c05ee..016de6db78 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Library PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] CalculatedHash; string CalculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Any(i => i == readyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { if (String.IsNullOrEmpty(readyHash.Salt)) { @@ -64,7 +64,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format("Requested crypto method not available in provider: {0}", readyHash.Id)); + 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); @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) { string hash = user.EasyPassword; - user.EasyPassword = String.Format("$SHA1${0}", hash); + user.EasyPassword = string.Format("$SHA1${0}", hash); } } } @@ -122,7 +122,8 @@ namespace Emby.Server.Implementations.Library passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); passwordHash.Id = _cryptographyProvider.DefaultHashMethod; passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); - }else if (newPassword != null) + } + else if (newPassword != null) { passwordHash.Hash = GetHashedString(user, newPassword); } From 48e7274d3783e57f89f6e1cc76fcd8696e987ec5 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 18 Feb 2019 01:26:01 -0800 Subject: [PATCH 011/166] added justaman notes, fixed new bug from emty has removals --- .../Cryptography/CryptographyProvider.cs | 5 ++- .../Library/DefaultAuthenticationProvider.cs | 42 ++++++++++++++----- .../Library/UserManager.cs | 4 +- .../Cryptography/PasswordHash.cs | 42 ++++++++++--------- 4 files changed, 59 insertions(+), 34 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 436443f066..c4f0346314 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Security.Cryptography; using System.Text; @@ -102,12 +103,12 @@ namespace Emby.Server.Implementations.Cryptography } else { - return PBKDF2(HashMethod, bytes, salt,defaultiterations); + return PBKDF2(HashMethod, bytes, salt, defaultiterations); } } else { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}")); + throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 016de6db78..80026d97c9 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -37,7 +37,17 @@ namespace Emby.Server.Implementations.Library if (resolvedUser == null) { throw new Exception("Invalid username or password"); - } + } + + //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); @@ -106,15 +116,30 @@ namespace Emby.Server.Implementations.Library return Task.FromResult(hasConfiguredPassword); } - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); + private bool IsPasswordEmpty(User user, string password) + { + if (string.IsNullOrEmpty(user.Password)) + { + return string.IsNullOrEmpty(password); + } + return false; } public Task ChangePassword(User user, string newPassword) { - //string newPasswordHash = null; - ConvertPasswordFormat(user); + 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; + } + PasswordHash passwordHash = new PasswordHash(user.Password); if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) { @@ -143,11 +168,6 @@ namespace Emby.Server.Implementations.Library return user.Password; } - public string GetEmptyHashedString(User user) - { - return null; - } - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) { passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index b8777a4802..3daed0c085 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -475,13 +475,13 @@ 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); + return string.IsNullOrEmpty(passwordHash); } /// diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index cd61657c18..3a817543bc 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -18,48 +18,52 @@ namespace MediaBrowser.Model.Cryptography public byte[] HashBytes; public PasswordHash(string storageString) { - string[] SplitStorageString = storageString.Split('$'); - Id = SplitStorageString[1]; - if (SplitStorageString[2].Contains("=")) + string[] splitted = storageString.Split('$'); + Id = splitted[1]; + if (splitted[2].Contains("=")) { - foreach (string paramset in (SplitStorageString[2].Split(','))) + foreach (string paramset in (splitted[2].Split(','))) { if (!String.IsNullOrEmpty(paramset)) { string[] fields = paramset.Split('='); - if(fields.Length == 2) + if (fields.Length == 2) { Parameters.Add(fields[0], fields[1]); + } + else + { + throw new Exception($"Malformed parameter in password hash string {paramset}"); } } } - if (SplitStorageString.Length == 5) + if (splitted.Length == 5) { - Salt = SplitStorageString[3]; + Salt = splitted[3]; SaltBytes = ConvertFromByteString(Salt); - Hash = SplitStorageString[4]; + Hash = splitted[4]; HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = SplitStorageString[3]; + Hash = splitted[3]; HashBytes = ConvertFromByteString(Hash); } } else { - if (SplitStorageString.Length == 4) + if (splitted.Length == 4) { - Salt = SplitStorageString[2]; + Salt = splitted[2]; SaltBytes = ConvertFromByteString(Salt); - Hash = SplitStorageString[3]; + Hash = splitted[3]; HashBytes = ConvertFromByteString(Hash); } else { Salt = string.Empty; - Hash = SplitStorageString[2]; + Hash = splitted[2]; HashBytes = ConvertFromByteString(Hash); } @@ -83,6 +87,7 @@ namespace MediaBrowser.Model.Cryptography } return Bytes.ToArray(); } + public static string ConvertToByteString(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", ""); @@ -104,19 +109,18 @@ namespace MediaBrowser.Model.Cryptography public override string ToString() { - string OutString = "$"; - OutString += Id; + string outString = "$" +Id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { - OutString += $"${paramstring}"; + outString += $"${paramstring}"; } if (!string.IsNullOrEmpty(Salt)) { - OutString += $"${Salt}"; + outString += $"${Salt}"; } - OutString += $"${Hash}"; - return OutString; + outString += $"${Hash}"; + return outString; } } From 56e306334201e06f2066e1e7ca1246508346bfa9 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 18 Feb 2019 10:56:01 -0800 Subject: [PATCH 012/166] little fixes for JustAMan --- .../Cryptography/CryptographyProvider.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index c4f0346314..dc528c280a 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -132,8 +132,7 @@ namespace Emby.Server.Implementations.Cryptography } catch (Exception e) { - iterations = defaultiterations; - throw new Exception($"Couldn't successfully parse iterations value from string:{hash.Parameters["iterations"]}", 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); From 6834b64922d2dd10b8e27943ec6630d1d95d6ae5 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Mon, 18 Feb 2019 22:26:40 +0100 Subject: [PATCH 013/166] Add first Azure Pipeline (Build) --- .azure/azure-pipelines.yml | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 .azure/azure-pipelines.yml diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml new file mode 100644 index 0000000000..ad6ba1ab27 --- /dev/null +++ b/.azure/azure-pipelines.yml @@ -0,0 +1,50 @@ +resources: +- repo: self + +pool: + vmImage: Hosted Ubuntu 1604 +#Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 +#Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 +#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 +#Your build pipeline references an undefined variable named ‘Parameters.TestProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 +#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 +#Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 +#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 + +steps: +- task: DotNetCoreCLI@2 + displayName: Restore + inputs: + command: restore + projects: '$(Parameters.RestoreBuildProjects)' + +- task: DotNetCoreCLI@2 + displayName: Build + inputs: + projects: '$(Parameters.RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration)' + +- task: DotNetCoreCLI@2 + displayName: Test + inputs: + command: test + projects: '$(Parameters.TestProjects)' + arguments: '--configuration $(BuildConfiguration)' + enabled: false + +- task: DotNetCoreCLI@2 + displayName: Publish + inputs: + command: publish + publishWebProjects: false + projects: '$(Parameters.RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' + zipAfterPublish: True + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + enabled: false + + From 18717d103af8f1360360bd1bb52e50c7bd4217fa Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Mon, 18 Feb 2019 22:36:24 +0100 Subject: [PATCH 014/166] Update azure-pipelines.yml for Azure Pipelines --- .azure/azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index ad6ba1ab27..513c7b6fc3 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,6 +1,12 @@ resources: - repo: self +pr: + branches: + include: + - master + - releases/* + pool: vmImage: Hosted Ubuntu 1604 #Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 From 60fc53306d3a5946dfb219da3163eaa49b99e4c5 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Mon, 18 Feb 2019 22:53:32 +0100 Subject: [PATCH 015/166] Fixed vmImage --- .azure/azure-pipelines.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index ad6ba1ab27..7ec002dd74 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,8 +1,14 @@ resources: -- repo: self + repositories: self + +pr: +include: +- master +- release-* + pool: - vmImage: Hosted Ubuntu 1604 + vmImage: ubuntu-16.04 #Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 #Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 #Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 From ef17ec700bd18861967c7933727eb0e4a36a767a Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Mon, 18 Feb 2019 23:24:39 +0100 Subject: [PATCH 016/166] Update azure-pipelines.yml --- .azure/azure-pipelines.yml | 72 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index fc239032e0..d2f4dc393a 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,17 +1,11 @@ resources: repositories: self -pr: -include: -- master -- release-* - - pr: branches: include: - master - - releases/* + - release-* pool: vmImage: ubuntu-16.04 @@ -23,40 +17,42 @@ pool: #Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 #Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 -steps: -- task: DotNetCoreCLI@2 - displayName: Restore - inputs: - command: restore - projects: '$(Parameters.RestoreBuildProjects)' +jobs: +- job: Build + steps: + - task: DotNetCoreCLI@2 + displayName: Restore + inputs: + command: restore + projects: '$(Parameters.RestoreBuildProjects)' -- task: DotNetCoreCLI@2 - displayName: Build - inputs: - projects: '$(Parameters.RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration)' + - task: DotNetCoreCLI@2 + displayName: Build + inputs: + projects: '$(Parameters.RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration)' -- task: DotNetCoreCLI@2 - displayName: Test - inputs: - command: test - projects: '$(Parameters.TestProjects)' - arguments: '--configuration $(BuildConfiguration)' - enabled: false + - task: DotNetCoreCLI@2 + displayName: Test + inputs: + command: test + projects: '$(Parameters.TestProjects)' + arguments: '--configuration $(BuildConfiguration)' + enabled: false -- task: DotNetCoreCLI@2 - displayName: Publish - inputs: - command: publish - publishWebProjects: false - projects: '$(Parameters.RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' - zipAfterPublish: True + - task: DotNetCoreCLI@2 + displayName: Publish + inputs: + command: publish + publishWebProjects: false + projects: '$(Parameters.RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' + zipAfterPublish: True -- task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - enabled: false + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + enabled: false From 480999e8e637354867f2dbf6db9216c9d7a72ebe Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Mon, 18 Feb 2019 23:42:24 +0100 Subject: [PATCH 017/166] Update azure-pipelines.yml for Azure Pipelines --- .azure/azure-pipelines.yml | 91 +++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index d2f4dc393a..a69f0ab9fb 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,58 +1,67 @@ -resources: - repositories: self - pr: + autoCancel: true + +trigger: + batch: true branches: include: - master - - release-* pool: vmImage: ubuntu-16.04 -#Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 -#Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 -#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 -#Your build pipeline references an undefined variable named ‘Parameters.TestProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 -#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 -#Your build pipeline references an undefined variable named ‘Parameters.RestoreBuildProjects’. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab. See https://go.microsoft.com/fwlink/?linkid=865972 + +variables: + - name: TestProjects + value: '' + - name: RestoreBuildProjects + value: 'Jellyfin.Server/Jellyfin.Server.csproj' + #Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 jobs: - job: Build - steps: - - task: DotNetCoreCLI@2 - displayName: Restore - inputs: - command: restore - projects: '$(Parameters.RestoreBuildProjects)' + steps: + - checkout: self + clean: false + submodules: true + persistCredentials: false - - task: DotNetCoreCLI@2 - displayName: Build - inputs: - projects: '$(Parameters.RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration)' + - task: DotNetCoreCLI@2 + displayName: Restore + inputs: + command: restore + projects: '$(RestoreBuildProjects)' - - task: DotNetCoreCLI@2 - displayName: Test - inputs: - command: test - projects: '$(Parameters.TestProjects)' - arguments: '--configuration $(BuildConfiguration)' - enabled: false + - task: DotNetCoreCLI@2 + displayName: Build + inputs: + projects: '$(RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration)' + + - task: DotNetCoreCLI@2 + displayName: Test + inputs: + command: test + projects: '$(RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration)' + enabled: false + + - task: DotNetCoreCLI@2 + displayName: Publish + inputs: + command: publish + publishWebProjects: false + projects: '$(RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' + zipAfterPublish: True + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + artifactName: 'build-$(BuildConfiguration)' + zipAfterPublish: false - - task: DotNetCoreCLI@2 - displayName: Publish - inputs: - command: publish - publishWebProjects: false - projects: '$(Parameters.RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' - zipAfterPublish: True - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - enabled: false From 7c9803e135eb7c02f4b759a4b0dc1b9a55b30af1 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Mon, 18 Feb 2019 23:47:31 +0100 Subject: [PATCH 018/166] Update azure-pipelines.yml for Azure Pipelines --- .azure/azure-pipelines.yml | 104 +++++++++++++++++++------------------ 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index a69f0ab9fb..3b48664956 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,3 +1,20 @@ +name: Build + +resources: + repositories: + - repository: self + type: github + name: EraYaN/jellyfin + +pool: + vmImage: ubuntu-16.04 + +variables: +- name: TestProjects + value: '' +- name: RestoreBuildProjects + value: 'Jellyfin.Server/Jellyfin.Server.csproj' + pr: autoCancel: true @@ -7,61 +24,48 @@ trigger: include: - master -pool: - vmImage: ubuntu-16.04 - -variables: - - name: TestProjects - value: '' - - name: RestoreBuildProjects - value: 'Jellyfin.Server/Jellyfin.Server.csproj' - #Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 jobs: -- job: Build - steps: - - checkout: self - clean: false - submodules: true - persistCredentials: false + - job: Build + steps: + - checkout: self + clean: false + submodules: true + persistCredentials: false - - task: DotNetCoreCLI@2 - displayName: Restore - inputs: - command: restore - projects: '$(RestoreBuildProjects)' - - - task: DotNetCoreCLI@2 - displayName: Build - inputs: - projects: '$(RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration)' - - - task: DotNetCoreCLI@2 - displayName: Test - inputs: - command: test - projects: '$(RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration)' - enabled: false - - - task: DotNetCoreCLI@2 - displayName: Publish - inputs: - command: publish - publishWebProjects: false - projects: '$(RestoreBuildProjects)' - arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' - zipAfterPublish: True - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - artifactName: 'build-$(BuildConfiguration)' - zipAfterPublish: false + - task: DotNetCoreCLI@2 + displayName: Restore + inputs: + command: restore + projects: '$(RestoreBuildProjects)' + - task: DotNetCoreCLI@2 + displayName: Build + inputs: + projects: '$(RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration)' + - task: DotNetCoreCLI@2 + displayName: Test + inputs: + command: test + projects: '$(RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration)' + enabled: false + - task: DotNetCoreCLI@2 + displayName: Publish + inputs: + command: publish + publishWebProjects: false + projects: '$(RestoreBuildProjects)' + arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' + zipAfterPublish: true + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)' + artifactName: 'build-$(BuildConfiguration)' + zipAfterPublish: false From 7bbcb455c0b6590d3e0e52948657de74b11bb3b6 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:17:57 +0100 Subject: [PATCH 019/166] Added compat checking to YAML --- .azure/azure-pipelines.yml | 135 +++++++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 19 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 3b48664956..057534608e 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,19 +1,10 @@ -name: Build - -resources: - repositories: - - repository: self - type: github - name: EraYaN/jellyfin - -pool: - vmImage: ubuntu-16.04 +name: $(Date:yyyyMMdd).$(Rev:.r) variables: -- name: TestProjects - value: '' -- name: RestoreBuildProjects - value: 'Jellyfin.Server/Jellyfin.Server.csproj' + - name: TestProjects + value: 'Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj' + - name: RestoreBuildProjects + value: 'Jellyfin.Server/Jellyfin.Server.csproj' pr: autoCancel: true @@ -22,12 +13,19 @@ trigger: batch: true branches: include: - - master - -#Your build pipeline references the ‘BuildConfiguration’ variable, which you’ve selected to be settable at queue time. Create or edit the build pipeline for this YAML file, define the variable on the Variables tab, and then select the option to make it settable at queue time. See https://go.microsoft.com/fwlink/?linkid=865971 + - master jobs: - - job: Build + - job: main_build + displayName: Main Build + pool: + vmImage: ubuntu-16.04 + strategy: + matrix: + release: + BuildConfiguration: Release + debug: + BuildConfiguration: Release steps: - checkout: self clean: false @@ -67,5 +65,104 @@ jobs: displayName: 'Publish Artifact' inputs: PathtoPublish: '$(build.artifactstagingdirectory)' - artifactName: 'build-$(BuildConfiguration)' + artifactName: 'jellyfin-build-$(BuildConfiguration)' zipAfterPublish: false + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact Naming' + condition: eq(variables['BuildConfiguration'], 'Release') + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Naming.dll' + artifactName: 'Jellyfin.Naming' + zipAfterPublish: false + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact Controller' + condition: eq(variables['BuildConfiguration'], 'Release') + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Controller.dll' + artifactName: 'Jellyfin.Controller' + zipAfterPublish: false + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact Model' + condition: eq(variables['BuildConfiguration'], 'Release') + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Model.dll' + artifactName: 'Jellyfin.Model' + zipAfterPublish: false + + - task: PublishBuildArtifacts@1 + displayName: 'Publish Artifact Common' + condition: eq(variables['BuildConfiguration'], 'Release') + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Common.dll' + artifactName: 'Jellyfin.Common' + zipAfterPublish: false + + - job: dotnet_compat + displayName: Compatibility Check + pool: + vmImage: ubuntu-16.04 + dependsOn: main_build + condition: success() + strategy: + matrix: + Naming: + NugetPackageName: Jellyfin.Naming + AssemblyName: Emby.Naming.dll + Controller: + NugetPackageName: Jellyfin.Controller + AssemblyName: MediaBrowser.Controller.dll + Model: + NugetPackageName: Jellyfin.Model + AssemblyName: MediaBrowser.Model.dll + Common: + NugetPackageName: Jellyfin.Common + AssemblyName: MediaBrowser.Common.dll + steps: + - checkout: none + + - task: NuGet@2 + displayName: 'Download $(NugetPackageName)' + inputs: + command: custom + arguments: 'install $(NugetPackageName) -OutputDirectory $(System.ArtifactsDirectory)/packages -ExcludeVersion -DirectDownload' + + - task: CopyFiles@2 + displayName: Copy Nuget Assembly to release folder + inputs: + sourceFolder: $(System.ArtifactsDirectory)/packages/$(NugetPackageName) # Optional + contents: '**/*.dll' + targetFolder: $(System.ArtifactsDirectory)/current-release + cleanTargetFolder: true # Optional + overWrite: true # Optional + flattenFolders: true # Optional + + - task: DownloadBuildArtifacts@0 + displayName: Download the Assembly Build Artifact + inputs: + buildType: 'current' # Options: current, specific + allowPartiallySucceededBuilds: false # Optional + downloadType: 'single' # Options: single, specific + artifactName: '$(NugetPackageName)' # Required when downloadType == Single + downloadPath: '$(System.ArtifactsDirectory)/new-release' + + - task: DownloadGitHubReleases@0 + displayName: Download ABI compatibility check tool from GitHub + inputs: + connection: GitHub (EraYaN) + userRepository: EraYaN/dotnet-compatibility + defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag + #version: # Required when defaultVersionType != Latest + itemPattern: '**-ci.zip' # Optional + downloadPath: '$(System.ArtifactsDirectory)' + + - task: ExtractFiles@1 + displayName: Extract ABI compatibility check tool + inputs: + archiveFilePatterns: '*-ci.zip' + destinationFolder: $(System.ArtifactsDirectory)\tools + cleanDestinationFolder: true + + From 5ef63e738dae8c9fda0a2cee2124aff627723fda Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:28:16 +0100 Subject: [PATCH 020/166] Added final stage and removed triggers. --- .azure/azure-pipelines.yml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 057534608e..87be79d0a4 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -110,16 +110,16 @@ jobs: matrix: Naming: NugetPackageName: Jellyfin.Naming - AssemblyName: Emby.Naming.dll + AssemblyFileName: Emby.Naming.dll Controller: NugetPackageName: Jellyfin.Controller - AssemblyName: MediaBrowser.Controller.dll + AssemblyFileName: MediaBrowser.Controller.dll Model: NugetPackageName: Jellyfin.Model - AssemblyName: MediaBrowser.Model.dll + AssemblyFileName: MediaBrowser.Model.dll Common: NugetPackageName: Jellyfin.Common - AssemblyName: MediaBrowser.Common.dll + AssemblyFileName: MediaBrowser.Common.dll steps: - checkout: none @@ -162,7 +162,14 @@ jobs: displayName: Extract ABI compatibility check tool inputs: archiveFilePatterns: '*-ci.zip' - destinationFolder: $(System.ArtifactsDirectory)\tools + destinationFolder: $(System.ArtifactsDirectory)/tools cleanDestinationFolder: true + - task: CmdLine@2 + displayName: Execute ABI compatibility check tool + inputs: + script: 'tools/CompatibilityCheckerCoreCLI current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)' + workingDirectory: $(System.ArtifactsDirectory) # Optional + #failOnStderr: false # Optional + From e2d0f6707756407f76e819a1c55928f1a133292f Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:32:36 +0100 Subject: [PATCH 021/166] Fix the conditions. --- .azure/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 87be79d0a4..5e8a9f9360 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -105,7 +105,7 @@ jobs: pool: vmImage: ubuntu-16.04 dependsOn: main_build - condition: success() + condition: succeeded() strategy: matrix: Naming: From d4bc7b5a5ae3a5e59e367e4832d8a929d433c46f Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:42:42 +0100 Subject: [PATCH 022/166] Fix NuGet task name. --- .azure/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 5e8a9f9360..38a355b96f 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -123,7 +123,7 @@ jobs: steps: - checkout: none - - task: NuGet@2 + - task: NuGetCommand@2 displayName: 'Download $(NugetPackageName)' inputs: command: custom From 18418b68926f42a16ba848ba2dc7135f4473682d Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:50:25 +0100 Subject: [PATCH 023/166] Fixed secondary Artifact paths and Debug config. --- .azure/azure-pipelines.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 38a355b96f..500d9d8c80 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -25,7 +25,7 @@ jobs: release: BuildConfiguration: Release debug: - BuildConfiguration: Release + BuildConfiguration: Debug steps: - checkout: self clean: false @@ -66,39 +66,35 @@ jobs: inputs: PathtoPublish: '$(build.artifactstagingdirectory)' artifactName: 'jellyfin-build-$(BuildConfiguration)' - zipAfterPublish: false + zipAfterPublish: true - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Naming' condition: eq(variables['BuildConfiguration'], 'Release') inputs: - PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Naming.dll' + PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Naming.dll' artifactName: 'Jellyfin.Naming' - zipAfterPublish: false - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Controller' condition: eq(variables['BuildConfiguration'], 'Release') inputs: - PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Controller.dll' + PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll' artifactName: 'Jellyfin.Controller' - zipAfterPublish: false - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Model' condition: eq(variables['BuildConfiguration'], 'Release') inputs: - PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Model.dll' + PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll' artifactName: 'Jellyfin.Model' - zipAfterPublish: false - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Common' condition: eq(variables['BuildConfiguration'], 'Release') inputs: - PathtoPublish: '$(build.artifactstagingdirectory)/MediaBrowser.Common.dll' + PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll' artifactName: 'Jellyfin.Common' - zipAfterPublish: false - job: dotnet_compat displayName: Compatibility Check From 9961c8c4597c0e83d88491ca87e3e7535f27a661 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:55:10 +0100 Subject: [PATCH 024/166] No full publish of artifacts. --- .azure/azure-pipelines.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 500d9d8c80..9fbb979676 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -59,14 +59,14 @@ jobs: publishWebProjects: false projects: '$(RestoreBuildProjects)' arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)' - zipAfterPublish: true + zipAfterPublish: false - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact' - inputs: - PathtoPublish: '$(build.artifactstagingdirectory)' - artifactName: 'jellyfin-build-$(BuildConfiguration)' - zipAfterPublish: true + # - task: PublishBuildArtifacts@1 + # displayName: 'Publish Artifact' + # inputs: + # PathtoPublish: '$(build.artifactstagingdirectory)' + # artifactName: 'jellyfin-build-$(BuildConfiguration)' + # zipAfterPublish: true - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Naming' From 6b1a64652fcbf087cca6a1c1817189d852797937 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 02:59:56 +0100 Subject: [PATCH 025/166] Fix path for naming, and add extra CopyFIle Task. --- .azure/azure-pipelines.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 9fbb979676..3e133290c4 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -72,7 +72,7 @@ jobs: displayName: 'Publish Artifact Naming' condition: eq(variables['BuildConfiguration'], 'Release') inputs: - PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Naming.dll' + PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll' artifactName: 'Jellyfin.Naming' - task: PublishBuildArtifacts@1 @@ -126,7 +126,7 @@ jobs: arguments: 'install $(NugetPackageName) -OutputDirectory $(System.ArtifactsDirectory)/packages -ExcludeVersion -DirectDownload' - task: CopyFiles@2 - displayName: Copy Nuget Assembly to release folder + displayName: Copy Nuget Assembly to current-release folder inputs: sourceFolder: $(System.ArtifactsDirectory)/packages/$(NugetPackageName) # Optional contents: '**/*.dll' @@ -142,7 +142,17 @@ jobs: allowPartiallySucceededBuilds: false # Optional downloadType: 'single' # Options: single, specific artifactName: '$(NugetPackageName)' # Required when downloadType == Single - downloadPath: '$(System.ArtifactsDirectory)/new-release' + downloadPath: '$(System.ArtifactsDirectory)/new-artifacts' + + - task: CopyFiles@2 + displayName: Copy Artifact Assembly to new-release folder + inputs: + sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional + contents: '**/*.dll' + targetFolder: $(System.ArtifactsDirectory)/new-release + cleanTargetFolder: true # Optional + overWrite: true # Optional + flattenFolders: true # Optional - task: DownloadGitHubReleases@0 displayName: Download ABI compatibility check tool from GitHub From 5238ba5d8f4987d0ccf79bf3f0fa065a31fead08 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 03:07:44 +0100 Subject: [PATCH 026/166] Fix tool extraction search pattern. --- .azure/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 3e133290c4..dcf69e2311 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -167,7 +167,7 @@ jobs: - task: ExtractFiles@1 displayName: Extract ABI compatibility check tool inputs: - archiveFilePatterns: '*-ci.zip' + archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip' destinationFolder: $(System.ArtifactsDirectory)/tools cleanDestinationFolder: true From 0849c4a4478581752197ae180643e6eaa3daaa1f Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 03:12:53 +0100 Subject: [PATCH 027/166] Switched to dotnet based execution again because of permissions. --- .azure/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index dcf69e2311..7437874bb5 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -174,7 +174,7 @@ jobs: - task: CmdLine@2 displayName: Execute ABI compatibility check tool inputs: - script: 'tools/CompatibilityCheckerCoreCLI current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)' + script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName)' workingDirectory: $(System.ArtifactsDirectory) # Optional #failOnStderr: false # Optional From 029bafdf9c92c8ca09d10e2dff7c90dbd5c33f09 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 19 Feb 2019 03:16:17 +0100 Subject: [PATCH 028/166] Changed build name template and limited number of parallel jobs. --- .azure/azure-pipelines.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index 7437874bb5..a14cf0aa3f 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -1,4 +1,4 @@ -name: $(Date:yyyyMMdd).$(Rev:.r) +name: $(Date:yyyyMMdd)$(Rev:.r) variables: - name: TestProjects @@ -26,6 +26,7 @@ jobs: BuildConfiguration: Release debug: BuildConfiguration: Debug + maxParallel: 2 steps: - checkout: self clean: false @@ -116,6 +117,7 @@ jobs: Common: NugetPackageName: Jellyfin.Common AssemblyFileName: MediaBrowser.Common.dll + maxParallel: 2 steps: - checkout: none From 6bbb968b578fe42224227b70e78825bbed5cfc6f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 20 Feb 2019 00:00:26 -0800 Subject: [PATCH 029/166] minor changes and return to netstandard --- .../Cryptography/CryptographyProvider.cs | 5 +- .../Data/SqliteUserRepository.cs | 3 +- .../Emby.Server.Implementations.csproj | 2 +- .../Library/DefaultAuthenticationProvider.cs | 33 ++++---- .../Library/UserManager.cs | 3 +- .../Cryptography/PasswordHash.cs | 81 +++++++++++-------- 6 files changed, 72 insertions(+), 55 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index dc528c280a..2f2fd95922 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -73,8 +73,9 @@ namespace Emby.Server.Implementations.Cryptography } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) - { - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations, new HashAlgorithmName(method))) + { + //downgrading for now as we need this library to be dotnetstandard compliant + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) { return r.GetBytes(32); } diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 1b6deae7d3..3df91f71cb 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Data if (!localUsersTableExists && TableExists(connection, "Users")) { TryMigrateToLocalUsersTable(connection); - } + } + RemoveEmptyPasswordHashes(); } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 86b2efe54d..8356a9501b 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,7 +34,7 @@ - netcoreapp2.1 + netstandard2.0 false diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 80026d97c9..2ac3ef424e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.Library string CalculatedHashString; if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) { - if (String.IsNullOrEmpty(readyHash.Salt)) + if (string.IsNullOrEmpty(readyHash.Salt)) { CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); @@ -65,7 +65,8 @@ namespace Emby.Server.Implementations.Library { CalculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); CalculatedHashString = BitConverter.ToString(CalculatedHash).Replace("-", string.Empty); - } + } + if (CalculatedHashString == readyHash.Hash) { success = true; @@ -95,18 +96,20 @@ namespace Emby.Server.Implementations.Library private void ConvertPasswordFormat(User user) { if (!string.IsNullOrEmpty(user.Password)) + { + return; + } + + if (!user.Password.Contains("$")) { - 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); - } + 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); } } @@ -122,6 +125,7 @@ namespace Emby.Server.Implementations.Library { return string.IsNullOrEmpty(password); } + return false; } @@ -188,7 +192,8 @@ namespace Emby.Server.Implementations.Library { ConvertPasswordFormat(user); passwordHash = new PasswordHash(user.Password); - } + } + if (passwordHash.SaltBytes != null) { //the password is modern format with PBKDF and we should take advantage of that diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 3daed0c085..b74006233e 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -221,9 +221,8 @@ namespace Emby.Server.Implementations.Library { //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 - string UserNameRegex = "^[\\w-'._@]*$"; // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.) - return Regex.IsMatch(username, UserNameRegex); + return Regex.IsMatch(username, "^[\\w-'._@]*$"); } private static bool IsValidUsernameCharacter(char i) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 3a817543bc..49bd510e96 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -10,26 +10,33 @@ namespace MediaBrowser.Model.Cryptography //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md //$[$=(,=)*][$[$]] - public string Id; - public Dictionary Parameters = new Dictionary(); - public string Salt; - public byte[] SaltBytes; - public string Hash; - public byte[] HashBytes; + private string id; + private Dictionary parameters = new Dictionary(); + private string salt; + private byte[] saltBytes; + private string hash; + private byte[] hashBytes; + public string Id { get => id; set => id = value; } + public Dictionary Parameters { get => parameters; set => parameters = value; } + public string Salt { get => salt; set => salt = value; } + public byte[] SaltBytes { get => saltBytes; set => saltBytes = value; } + public string Hash { get => hash; set => hash = value; } + public byte[] HashBytes { get => hashBytes; set => hashBytes = value; } + public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - Id = splitted[1]; + id = splitted[1]; if (splitted[2].Contains("=")) { foreach (string paramset in (splitted[2].Split(','))) { - if (!String.IsNullOrEmpty(paramset)) + if (!string.IsNullOrEmpty(paramset)) { string[] fields = paramset.Split('='); if (fields.Length == 2) { - Parameters.Add(fields[0], fields[1]); + parameters.Add(fields[0], fields[1]); } else { @@ -39,32 +46,32 @@ namespace MediaBrowser.Model.Cryptography } if (splitted.Length == 5) { - Salt = splitted[3]; - SaltBytes = ConvertFromByteString(Salt); - Hash = splitted[4]; - HashBytes = ConvertFromByteString(Hash); + salt = splitted[3]; + saltBytes = ConvertFromByteString(salt); + hash = splitted[4]; + hashBytes = ConvertFromByteString(hash); } else { - Salt = string.Empty; - Hash = splitted[3]; - HashBytes = ConvertFromByteString(Hash); + salt = string.Empty; + hash = splitted[3]; + hashBytes = ConvertFromByteString(hash); } } else { if (splitted.Length == 4) { - Salt = splitted[2]; - SaltBytes = ConvertFromByteString(Salt); - Hash = splitted[3]; - HashBytes = ConvertFromByteString(Hash); + salt = splitted[2]; + saltBytes = ConvertFromByteString(salt); + hash = splitted[3]; + hashBytes = ConvertFromByteString(hash); } else { - Salt = string.Empty; - Hash = splitted[2]; - HashBytes = ConvertFromByteString(Hash); + salt = string.Empty; + hash = splitted[2]; + hashBytes = ConvertFromByteString(hash); } } @@ -73,9 +80,9 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(ICryptoProvider cryptoProvider) { - Id = cryptoProvider.DefaultHashMethod; - SaltBytes = cryptoProvider.GenerateSalt(); - Salt = ConvertToByteString(SaltBytes); + id = cryptoProvider.DefaultHashMethod; + saltBytes = cryptoProvider.GenerateSalt(); + salt = ConvertToByteString(SaltBytes); } public static byte[] ConvertFromByteString(string byteString) @@ -95,31 +102,35 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { - string ReturnString = String.Empty; - foreach (var KVP in Parameters) + string ReturnString = string.Empty; + foreach (var KVP in parameters) { - ReturnString += String.Format(",{0}={1}", KVP.Key, KVP.Value); - } + ReturnString += $",{KVP.Key}={KVP.Value}"; + } + if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); - } + } + return ReturnString; } public override string ToString() { - string outString = "$" +Id; + string outString = "$" +id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { outString += $"${paramstring}"; } - if (!string.IsNullOrEmpty(Salt)) + + if (!string.IsNullOrEmpty(salt)) { - outString += $"${Salt}"; + outString += $"${salt}"; } - outString += $"${Hash}"; + + outString += $"${hash}"; return outString; } } From 098de6b0501eaa0375fc5bfd7cff369815b57718 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 20 Feb 2019 01:17:30 -0800 Subject: [PATCH 030/166] made newlines into linux newlines --- .../Cryptography/CryptographyProvider.cs | 294 +++++----- .../Data/SqliteUserRepository.cs | 526 +++++++++--------- .../Library/DefaultAuthenticationProvider.cs | 360 ++++++------ 3 files changed, 589 insertions(+), 591 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 2f2fd95922..ea719309cd 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -1,149 +1,149 @@ -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Cryptography -{ - public class CryptographyProvider : ICryptoProvider - { - private HashSet SupportedHashMethods; - public string DefaultHashMethod => "SHA256"; - private RandomNumberGenerator rng; - private int defaultiterations = 1000; - public CryptographyProvider() - { - //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 - SupportedHashMethods = new HashSet() - { - "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" - }; - rng = RandomNumberGenerator.Create(); - } - - public Guid GetMD5(string str) - { - return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); - } - - public byte[] ComputeSHA1(byte[] bytes) - { - using (var provider = SHA1.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public byte[] ComputeMD5(Stream str) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(str); - } - } - - public byte[] ComputeMD5(byte[] bytes) - { - using (var provider = MD5.Create()) - { - return provider.ComputeHash(bytes); - } - } - - public IEnumerable GetSupportedHashMethods() - { - return SupportedHashMethods; - } - - private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) +using System.IO; +using System.Security.Cryptography; +using System.Text; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Cryptography +{ + public class CryptographyProvider : ICryptoProvider + { + private HashSet SupportedHashMethods; + public string DefaultHashMethod => "SHA256"; + private RandomNumberGenerator rng; + private int defaultiterations = 1000; + public CryptographyProvider() { - //downgrading for now as we need this library to be dotnetstandard compliant - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) - { - return r.GetBytes(32); - } - } - - public byte[] ComputeHash(string HashMethod, byte[] bytes) - { - return ComputeHash(HashMethod, bytes, new byte[0]); - } - - public byte[] ComputeHashWithDefaultMethod(byte[] bytes) - { - return ComputeHash(DefaultHashMethod, bytes); - } - - public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) - { - if (SupportedHashMethods.Contains(HashMethod)) - { - if (salt.Length == 0) - { - using (var h = HashAlgorithm.Create(HashMethod)) - { - return h.ComputeHash(bytes); - } - } - else - { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); - } - } - 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]; - rng.GetBytes(salt); - return salt; - } - } -} + //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 + SupportedHashMethods = new HashSet() + { + "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" + }; + rng = RandomNumberGenerator.Create(); + } + + public Guid GetMD5(string str) + { + return new Guid(ComputeMD5(Encoding.Unicode.GetBytes(str))); + } + + public byte[] ComputeSHA1(byte[] bytes) + { + using (var provider = SHA1.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public byte[] ComputeMD5(Stream str) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(str); + } + } + + public byte[] ComputeMD5(byte[] bytes) + { + using (var provider = MD5.Create()) + { + return provider.ComputeHash(bytes); + } + } + + public IEnumerable 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 + using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + { + return r.GetBytes(32); + } + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes) + { + return ComputeHash(HashMethod, bytes, new byte[0]); + } + + public byte[] ComputeHashWithDefaultMethod(byte[] bytes) + { + return ComputeHash(DefaultHashMethod, bytes); + } + + public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + { + if (SupportedHashMethods.Contains(HashMethod)) + { + if (salt.Length == 0) + { + using (var h = HashAlgorithm.Create(HashMethod)) + { + return h.ComputeHash(bytes); + } + } + else + { + return PBKDF2(HashMethod, bytes, salt, defaultiterations); + } + } + 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]; + rng.GetBytes(salt); + return salt; + } + } +} diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index 3df91f71cb..182df0edc9 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -1,264 +1,264 @@ -using System; -using System.Collections.Generic; -using System.IO; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using SQLitePCL.pretty; - -namespace Emby.Server.Implementations.Data -{ - /// - /// Class SQLiteUserRepository - /// - public class SqliteUserRepository : BaseSqliteRepository, IUserRepository - { - private readonly IJsonSerializer _jsonSerializer; - - public SqliteUserRepository( - ILoggerFactory loggerFactory, - IServerApplicationPaths appPaths, - IJsonSerializer jsonSerializer) - : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) - { - _jsonSerializer = jsonSerializer; - - DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); - } - - /// - /// Gets the name of the repository - /// - /// The name. - public string Name => "SQLite"; - - /// - /// Opens the connection to the database - /// - /// Task. - public void Initialize() - { - using (var connection = CreateConnection()) - { - RunDefaultInitialization(connection); - - var localUsersTableExists = TableExists(connection, "LocalUsersv2"); - - connection.RunQueries(new[] { - "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", - "drop index if exists idx_users" - }); - - if (!localUsersTableExists && TableExists(connection, "Users")) - { - TryMigrateToLocalUsersTable(connection); +using System; +using System.Collections.Generic; +using System.IO; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Serialization; +using Microsoft.Extensions.Logging; +using SQLitePCL.pretty; + +namespace Emby.Server.Implementations.Data +{ + /// + /// Class SQLiteUserRepository + /// + public class SqliteUserRepository : BaseSqliteRepository, IUserRepository + { + private readonly IJsonSerializer _jsonSerializer; + + public SqliteUserRepository( + ILoggerFactory loggerFactory, + IServerApplicationPaths appPaths, + IJsonSerializer jsonSerializer) + : base(loggerFactory.CreateLogger(nameof(SqliteUserRepository))) + { + _jsonSerializer = jsonSerializer; + + DbFilePath = Path.Combine(appPaths.DataPath, "users.db"); + } + + /// + /// Gets the name of the repository + /// + /// The name. + public string Name => "SQLite"; + + /// + /// Opens the connection to the database + /// + /// Task. + public void Initialize() + { + using (var connection = CreateConnection()) + { + RunDefaultInitialization(connection); + + var localUsersTableExists = TableExists(connection, "LocalUsersv2"); + + connection.RunQueries(new[] { + "create table if not exists LocalUsersv2 (Id INTEGER PRIMARY KEY, guid GUID NOT NULL, data BLOB NOT NULL)", + "drop index if exists idx_users" + }); + + if (!localUsersTableExists && TableExists(connection, "Users")) + { + TryMigrateToLocalUsersTable(connection); } - - RemoveEmptyPasswordHashes(); - } - } - - private void TryMigrateToLocalUsersTable(ManagedConnection connection) - { - try - { - connection.RunQueries(new[] - { - "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" - }); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error migrating users database"); - } - } - - 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); - } - } - - } - - /// - /// Save a user in the repo - /// - public void CreateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - var serialized = _jsonSerializer.SerializeToBytes(user); - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) - { - statement.TryBind("@guid", user.Id.ToGuidBlob()); - statement.TryBind("@data", serialized); - - statement.MoveNext(); - } - - var createdUser = GetUser(user.Id, false); - - if (createdUser == null) - { - throw new ApplicationException("created user should never be null"); - } - - user.InternalId = createdUser.InternalId; - - }, TransactionMode); - } - } - } - - public void UpdateUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - 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); - } - } - } - - private User GetUser(Guid guid, bool openLock) - { - using (openLock ? WriteLock.Read() : null) - { - using (var connection = CreateConnection(true)) - { - using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) - { - statement.TryBind("@guid", guid); - - foreach (var row in statement.ExecuteQuery()) - { - return GetUser(row); - } - } - } - } - - return null; - } - - private User GetUser(IReadOnlyList row) - { - var id = row[0].ToInt64(); - var guid = row[1].ReadGuidFromBlob(); - - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream(stream); - user.InternalId = id; - user.Id = guid; - return user; - } - } - - /// - /// Retrieve all users from the database - /// - /// IEnumerable{User}. - public List RetrieveAllUsers() - { - var list = new List(); - - using (WriteLock.Read()) - { - using (var connection = CreateConnection(true)) - { - foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) - { - list.Add(GetUser(row)); - } - } - } - - return list; - } - - /// - /// Deletes the user. - /// - /// The user. - /// Task. - /// user - public void DeleteUser(User user) - { - if (user == null) - { - throw new ArgumentNullException(nameof(user)); - } - - using (WriteLock.Write()) - { - using (var connection = CreateConnection()) - { - connection.RunInTransaction(db => - { - using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) - { - statement.TryBind("@id", user.InternalId); - statement.MoveNext(); - } - }, TransactionMode); - } - } - } - } -} + + RemoveEmptyPasswordHashes(); + } + } + + private void TryMigrateToLocalUsersTable(ManagedConnection connection) + { + try + { + connection.RunQueries(new[] + { + "INSERT INTO LocalUsersv2 (guid, data) SELECT guid,data from users" + }); + } + catch (Exception ex) + { + Logger.LogError(ex, "Error migrating users database"); + } + } + + 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); + } + } + + } + + /// + /// Save a user in the repo + /// + public void CreateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var serialized = _jsonSerializer.SerializeToBytes(user); + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("insert into LocalUsersv2 (guid, data) values (@guid, @data)")) + { + statement.TryBind("@guid", user.Id.ToGuidBlob()); + statement.TryBind("@data", serialized); + + statement.MoveNext(); + } + + var createdUser = GetUser(user.Id, false); + + if (createdUser == null) + { + throw new ApplicationException("created user should never be null"); + } + + user.InternalId = createdUser.InternalId; + + }, TransactionMode); + } + } + } + + public void UpdateUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + 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); + } + } + } + + private User GetUser(Guid guid, bool openLock) + { + using (openLock ? WriteLock.Read() : null) + { + using (var connection = CreateConnection(true)) + { + using (var statement = connection.PrepareStatement("select id,guid,data from LocalUsersv2 where guid=@guid")) + { + statement.TryBind("@guid", guid); + + foreach (var row in statement.ExecuteQuery()) + { + return GetUser(row); + } + } + } + } + + return null; + } + + private User GetUser(IReadOnlyList row) + { + var id = row[0].ToInt64(); + var guid = row[1].ReadGuidFromBlob(); + + using (var stream = new MemoryStream(row[2].ToBlob())) + { + stream.Position = 0; + var user = _jsonSerializer.DeserializeFromStream(stream); + user.InternalId = id; + user.Id = guid; + return user; + } + } + + /// + /// Retrieve all users from the database + /// + /// IEnumerable{User}. + public List RetrieveAllUsers() + { + var list = new List(); + + using (WriteLock.Read()) + { + using (var connection = CreateConnection(true)) + { + foreach (var row in connection.Query("select id,guid,data from LocalUsersv2")) + { + list.Add(GetUser(row)); + } + } + } + + return list; + } + + /// + /// Deletes the user. + /// + /// The user. + /// Task. + /// user + public void DeleteUser(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + using (WriteLock.Write()) + { + using (var connection = CreateConnection()) + { + connection.RunInTransaction(db => + { + using (var statement = db.PrepareStatement("delete from LocalUsersv2 where Id=@id")) + { + statement.TryBind("@id", user.InternalId); + statement.MoveNext(); + } + }, TransactionMode); + } + } + } + } +} diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 2ac3ef424e..b58374adb4 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -1,42 +1,42 @@ -using System; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Controller.Authentication; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Model.Cryptography; - -namespace Emby.Server.Implementations.Library -{ - public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser - { - private readonly ICryptoProvider _cryptographyProvider; - public DefaultAuthenticationProvider(ICryptoProvider crypto) - { - _cryptographyProvider = crypto; - } - - 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 Authenticate(string username, string password) - { - throw new NotImplementedException(); - } - - - //This is the verson that we need to use for local users. Because reasons. - public Task Authenticate(string username, string password, User resolvedUser) - { - bool success = false; - if (resolvedUser == null) - { - throw new Exception("Invalid username or password"); +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Authentication; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Cryptography; + +namespace Emby.Server.Implementations.Library +{ + public class DefaultAuthenticationProvider : IAuthenticationProvider, IRequiresResolvedUser + { + private readonly ICryptoProvider _cryptographyProvider; + public DefaultAuthenticationProvider(ICryptoProvider crypto) + { + _cryptographyProvider = crypto; + } + + 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 Authenticate(string username, string password) + { + throw new NotImplementedException(); + } + + + //This is the verson that we need to use for local users. Because reasons. + public Task Authenticate(string username, string password, User resolvedUser) + { + bool success = false; + if (resolvedUser == null) + { + throw new Exception("Invalid username or password"); } //As long as jellyfin supports passwordless users, we need this little block here to accomodate @@ -47,166 +47,164 @@ namespace Emby.Server.Implementations.Library 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); + + 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); } - - 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) - { - throw new Exception("Invalid username or password"); - } - - return Task.FromResult(new ProviderAuthenticationResult - { - Username = username - }); - } - - //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)) + 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) + { + throw new Exception("Invalid username or password"); + } + + return Task.FromResult(new ProviderAuthenticationResult + { + Username = username + }); + } + + //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 HasPassword(User user) - { - var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); - return Task.FromResult(hasConfiguredPassword); - } - - private bool IsPasswordEmpty(User user, string password) + + 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 HasPassword(User user) + { + var hasConfiguredPassword = !IsPasswordEmpty(user, GetPasswordHash(user)); + return Task.FromResult(hasConfiguredPassword); + } + + private bool IsPasswordEmpty(User user, string password) { if (string.IsNullOrEmpty(user.Password)) { return string.IsNullOrEmpty(password); } - return false; - } - - public Task ChangePassword(User user, string newPassword) - { + return false; + } + + public Task ChangePassword(User user, string newPassword) + { 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.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; } - PasswordHash passwordHash = new PasswordHash(user.Password); - if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) - { - 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(passwordHash.Hash)) - { - throw new ArgumentNullException(nameof(passwordHash.Hash)); - } - - user.Password = passwordHash.ToString(); - - return Task.CompletedTask; - } - - public string GetPasswordHash(User user) - { - return user.Password; - } - - public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) - { - passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); - return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); - } - - /// - /// Gets the hashed string. - /// - public string GetHashedString(User user, string str) - { - PasswordHash passwordHash; - if (String.IsNullOrEmpty(user.Password)) - { - passwordHash = new PasswordHash(_cryptographyProvider); - } - else - { - ConvertPasswordFormat(user); - passwordHash = new PasswordHash(user.Password); + PasswordHash passwordHash = new PasswordHash(user.Password); + if (passwordHash.Id == "SHA1" && string.IsNullOrEmpty(passwordHash.Salt)) + { + passwordHash.SaltBytes = _cryptographyProvider.GenerateSalt(); + passwordHash.Salt = PasswordHash.ConvertToByteString(passwordHash.SaltBytes); + passwordHash.Id = _cryptographyProvider.DefaultHashMethod; + passwordHash.Hash = GetHashedStringChangeAuth(newPassword, passwordHash); } - - 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))); - } - - - } - } -} + else if (newPassword != null) + { + passwordHash.Hash = GetHashedString(user, newPassword); + } + + if (string.IsNullOrWhiteSpace(passwordHash.Hash)) + { + throw new ArgumentNullException(nameof(passwordHash.Hash)); + } + + user.Password = passwordHash.ToString(); + + return Task.CompletedTask; + } + + public string GetPasswordHash(User user) + { + return user.Password; + } + + public string GetHashedStringChangeAuth(string newPassword, PasswordHash passwordHash) + { + passwordHash.HashBytes = Encoding.UTF8.GetBytes(newPassword); + return PasswordHash.ConvertToByteString(_cryptographyProvider.ComputeHash(passwordHash)); + } + + /// + /// Gets the hashed string. + /// + public string GetHashedString(User user, string str) + { + PasswordHash passwordHash; + if (String.IsNullOrEmpty(user.Password)) + { + passwordHash = new PasswordHash(_cryptographyProvider); + } + else + { + ConvertPasswordFormat(user); + passwordHash = new PasswordHash(user.Password); + } + + 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))); + } + } + } +} From 4db31acff940dc9cabbd39649103faf4dc2e2c47 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 21 Feb 2019 10:07:21 +0100 Subject: [PATCH 031/166] Begin removing System.Net sources --- .../HttpClientManager/HttpClientManager.cs | 2 +- .../HttpServer/FileWriter.cs | 16 +- .../HttpServer/HttpListenerHost.cs | 3 + .../HttpServer/HttpResultFactory.cs | 2 +- .../HttpServer/RangeRequestWriter.cs | 2 +- .../HttpServer/ResponseFilter.cs | 2 +- .../HttpServer/StreamWriter.cs | 4 +- .../SocketSharp/WebSocketSharpListener.cs | 56 +- .../SocketSharp/WebSocketSharpRequest.cs | 3 +- .../SocketSharp/WebSocketSharpResponse.cs | 46 +- .../BaseProgressiveStreamingService.cs | 18 +- MediaBrowser.Model/Services/IRequest.cs | 2 +- .../Services/QueryParamCollection.cs | 31 + SocketHttpListener/Ext.cs | 1 - SocketHttpListener/HttpResponse.cs | 243 ++++---- .../Net/AuthenticationSchemeSelector.cs | 6 - SocketHttpListener/Net/AuthenticationTypes.cs | 9 - SocketHttpListener/Net/BoundaryType.cs | 11 - SocketHttpListener/Net/ChunkStream.cs | 385 ------------- SocketHttpListener/Net/ChunkedInputStream.cs | 178 ------ SocketHttpListener/Net/EntitySendFormat.cs | 8 - SocketHttpListener/Net/HttpConnection.cs | 528 ----------------- .../Net/HttpEndPointListener.cs | 526 ----------------- SocketHttpListener/Net/HttpEndPointManager.cs | 192 ------- .../Net/HttpKnownHeaderNames.cs | 91 --- SocketHttpListener/Net/HttpListener.cs | 284 --------- .../Net/HttpListenerBasicIdentity.cs | 49 -- .../Net/HttpListenerContext.Managed.cs | 99 ---- SocketHttpListener/Net/HttpListenerContext.cs | 74 --- .../Net/HttpListenerPrefixCollection.cs | 117 ---- .../Net/HttpListenerRequest.Managed.cs | 325 ----------- SocketHttpListener/Net/HttpListenerRequest.cs | 537 ------------------ .../Net/HttpListenerRequestUriBuilder.cs | 443 --------------- .../Net/HttpListenerResponse.Managed.cs | 333 ----------- .../Net/HttpListenerResponse.cs | 294 ---------- .../Net/HttpRequestStream.Managed.cs | 210 ------- SocketHttpListener/Net/HttpRequestStream.cs | 129 ----- .../Net/HttpResponseStream.Managed.cs | 329 ----------- SocketHttpListener/Net/HttpResponseStream.cs | 124 ---- SocketHttpListener/Net/HttpStatusCode.cs | 321 ----------- .../Net/HttpStatusDescription.cs | 69 --- .../Net/HttpStreamAsyncResult.cs | 79 --- SocketHttpListener/Net/HttpVersion.cs | 16 - SocketHttpListener/Net/ListenerPrefix.cs | 89 --- SocketHttpListener/Net/UriScheme.cs | 20 - SocketHttpListener/Net/WebHeaderCollection.cs | 360 ------------ SocketHttpListener/Net/WebHeaderEncoding.cs | 84 --- .../HttpListenerWebSocketContext.cs | 92 --- .../Net/WebSockets/HttpWebSocket.Managed.cs | 81 --- .../Net/WebSockets/HttpWebSocket.cs | 159 ------ .../Net/WebSockets/WebSocketCloseStatus.cs | 27 - .../Net/WebSockets/WebSocketContext.cs | 24 - .../Net/WebSockets/WebSocketValidate.cs | 141 ----- SocketHttpListener/WebSocket.cs | 3 +- 54 files changed, 264 insertions(+), 7013 deletions(-) delete mode 100644 SocketHttpListener/Net/AuthenticationSchemeSelector.cs delete mode 100644 SocketHttpListener/Net/AuthenticationTypes.cs delete mode 100644 SocketHttpListener/Net/BoundaryType.cs delete mode 100644 SocketHttpListener/Net/ChunkStream.cs delete mode 100644 SocketHttpListener/Net/ChunkedInputStream.cs delete mode 100644 SocketHttpListener/Net/EntitySendFormat.cs delete mode 100644 SocketHttpListener/Net/HttpConnection.cs delete mode 100644 SocketHttpListener/Net/HttpEndPointListener.cs delete mode 100644 SocketHttpListener/Net/HttpEndPointManager.cs delete mode 100644 SocketHttpListener/Net/HttpKnownHeaderNames.cs delete mode 100644 SocketHttpListener/Net/HttpListener.cs delete mode 100644 SocketHttpListener/Net/HttpListenerBasicIdentity.cs delete mode 100644 SocketHttpListener/Net/HttpListenerContext.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerContext.cs delete mode 100644 SocketHttpListener/Net/HttpListenerPrefixCollection.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequest.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequest.cs delete mode 100644 SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs delete mode 100644 SocketHttpListener/Net/HttpListenerResponse.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpListenerResponse.cs delete mode 100644 SocketHttpListener/Net/HttpRequestStream.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpRequestStream.cs delete mode 100644 SocketHttpListener/Net/HttpResponseStream.Managed.cs delete mode 100644 SocketHttpListener/Net/HttpResponseStream.cs delete mode 100644 SocketHttpListener/Net/HttpStatusCode.cs delete mode 100644 SocketHttpListener/Net/HttpStatusDescription.cs delete mode 100644 SocketHttpListener/Net/HttpStreamAsyncResult.cs delete mode 100644 SocketHttpListener/Net/HttpVersion.cs delete mode 100644 SocketHttpListener/Net/ListenerPrefix.cs delete mode 100644 SocketHttpListener/Net/UriScheme.cs delete mode 100644 SocketHttpListener/Net/WebHeaderCollection.cs delete mode 100644 SocketHttpListener/Net/WebHeaderEncoding.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs delete mode 100644 SocketHttpListener/Net/WebSockets/HttpWebSocket.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketContext.cs delete mode 100644 SocketHttpListener/Net/WebSockets/WebSocketValidate.cs diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 2e07281369..834a494f81 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -327,7 +327,7 @@ namespace Emby.Server.Implementations.HttpClientManager } httpWebRequest.ContentType = contentType; - httpWebRequest.ContentLength = bytes.Length; + // httpWebRequest.ContentLength = bytes.Length; (await httpWebRequest.GetRequestStreamAsync().ConfigureAwait(false)).Write(bytes, 0, bytes.Length); } catch (Exception ex) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 7aedba9b31..835c6f52ef 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -5,6 +5,7 @@ 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; @@ -14,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer public class FileWriter : IHttpResult { private ILogger Logger { get; set; } + public IFileSystem FileSystem { get; } private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } @@ -51,6 +53,7 @@ namespace Emby.Server.Implementations.HttpServer Path = path; Logger = logger; + FileSystem = fileSystem; RangeHeader = rangeHeader; Headers["Content-Type"] = contentType; @@ -60,7 +63,8 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { - Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); + // TODO + //Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); StatusCode = HttpStatusCode.OK; } else @@ -95,7 +99,7 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - Headers["Content-Length"] = lengthString; + // TODO Headers["Content-Length"] = lengthString; var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); Headers["Content-Range"] = rangeString; @@ -174,12 +178,12 @@ namespace Emby.Server.Implementations.HttpServer } //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - - await response.TransmitFile(path, 0, 0, FileShare, cancellationToken).ConfigureAwait(false); + // TODO not DI friendly lol + await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); return; } - - await response.TransmitFile(path, RangeStart, RangeLength, FileShare, cancellationToken).ConfigureAwait(false); + // TODO not DI friendly lol + await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); } finally { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index ee746c6696..fb42460f1b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -644,6 +644,9 @@ namespace Emby.Server.Implementations.HttpServer { var bOutput = Encoding.UTF8.GetBytes(text); response.SetContentLength(bOutput.Length); + // TODO + response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID + response.SendChunked = true; return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 070717d489..5e9d2b4c36 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -592,7 +592,7 @@ namespace Emby.Server.Implementations.HttpServer { if (totalContentLength.HasValue) { - responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); + // TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); } if (isHeadRequest) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 891a76ec2a..9c8ab8d91c 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - Headers["Content-Length"] = RangeLength.ToString(UsCulture); + // TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture); Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", 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 da2bf983a0..dbce3250d4 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -25,7 +25,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", "*"); diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index cb2e3580b2..750b0f7958 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -56,7 +56,7 @@ namespace Emby.Server.Implementations.HttpServer if (source.CanSeek) { - Headers["Content-Length"] = source.Length.ToString(UsCulture); + // TODO Headers["Content-Length"] = source.Length.ToString(UsCulture); } } @@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.HttpServer Headers["Content-Type"] = contentType; - Headers["Content-Length"] = contentLength.ToString(UsCulture); + // TODO Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 693c2328c6..64856f04af 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; + using System.Net; + using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; @@ -13,9 +14,8 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; -using SocketHttpListener.Net; -namespace Jellyfin.Server.SocketSharp + namespace Jellyfin.Server.SocketSharp { public class WebSocketSharpListener : IHttpListener { @@ -67,24 +67,44 @@ namespace Jellyfin.Server.SocketSharp public void Start(IEnumerable urlPrefixes) { + // TODO + //if (_listener == null) + //{ + // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); + //} + + //_listener.EnableDualMode = _enableDualMode; + + //if (_certificate != null) + //{ + // _listener.LoadCert(_certificate); + //} + + //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); + //_listener.Prefixes.AddRange(urlPrefixes); + + //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); + + //_listener.Start(); + if (_listener == null) { - _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); + _listener = new HttpListener(); } - - _listener.EnableDualMode = _enableDualMode; - - if (_certificate != null) - { - _listener.LoadCert(_certificate); - } - + _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - _listener.Prefixes.AddRange(urlPrefixes); - _listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); + //foreach (var urlPrefix in urlPrefixes) + //{ + // _listener.Prefixes.Add(urlPrefix); + //} + _listener.Prefixes.Add("http://localhost:8096/"); _listener.Start(); + + // TODO how to do this in netcore? + _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), + null); } private static void LogRequest(ILogger logger, HttpListenerRequest request) @@ -98,8 +118,10 @@ namespace Jellyfin.Server.SocketSharp request.UserAgent ?? string.Empty); } - private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken) + private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) { + var context = _listener.EndGetContext(asyncResult); + _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); IHttpRequest httpReq = null; var request = context.Request; @@ -134,7 +156,7 @@ namespace Jellyfin.Server.SocketSharp var endpoint = ctx.Request.RemoteEndPoint.ToString(); var url = ctx.Request.RawUrl; - var queryString = ctx.Request.QueryString; + var queryString = new QueryParamCollection(ctx.Request.QueryString); var connectingArgs = new WebSocketConnectingEventArgs { @@ -153,7 +175,7 @@ namespace Jellyfin.Server.SocketSharp if (WebSocketConnected != null) { - var socket = new SharpWebSocket(webSocketContext.WebSocket, _logger); + SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs index 6458707d92..6a00e283c6 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Net; using System.Text; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; @@ -438,7 +439,7 @@ namespace Jellyfin.Server.SocketSharp public string UserAgent => request.UserAgent; - public QueryParamCollection Headers => request.Headers; + public QueryParamCollection Headers => new QueryParamCollection(request.Headers); private QueryParamCollection queryString; public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs index cf5aee5d40..552ae1754d 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using Emby.Server.Implementations; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; -using HttpListenerResponse = SocketHttpListener.Net.HttpListenerResponse; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; @@ -53,7 +54,7 @@ namespace Jellyfin.Server.SocketSharp set => _response.ContentType = value; } - public QueryParamCollection Headers => _response.Headers; + public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); private static string AsHeaderValue(Cookie cookie) { @@ -152,7 +153,7 @@ namespace Jellyfin.Server.SocketSharp // you can happily set the Content-Length header in Asp.Net // but HttpListener will complain if you do - you have to set ContentLength64 on the response. // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - _response.ContentLength64 = contentLength; + //_response.ContentLength64 = contentLength; } public void SetCookie(Cookie cookie) @@ -172,10 +173,43 @@ namespace Jellyfin.Server.SocketSharp public void ClearCookies() { } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) + const int StreamCopyToBufferSize = 81920; + public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { - return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); + // TODO + // return _response.TransmitFile(path, offset, count, fileShareMode, 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/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 6a98c5e8a6..3e74d59db7 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -324,7 +324,8 @@ namespace MediaBrowser.Api.Playback.Progressive // Seeing cases of -1 here if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) { - responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); + // TODO + //responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); } if (isHeadRequest) @@ -382,17 +383,18 @@ namespace MediaBrowser.Api.Playback.Progressive var hasHeaders = streamResult as IHasHeaders; if (hasHeaders != null) { - if (contentLength.HasValue) - { - hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - } - else - { + // TODO + //if (contentLength.HasValue) + //{ + // hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); + //} + //else + //{ if (hasHeaders.Headers.ContainsKey("Content-Length")) { hasHeaders.Headers.Remove("Content-Length"); } - } + //} } return streamResult; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index ac9b981b98..909cebce3d 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -152,7 +152,7 @@ namespace MediaBrowser.Model.Services QueryParamCollection Headers { get; } - Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken); + Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); bool SendChunked { get; set; } } diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 4297b97c66..0e0ebf848b 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,13 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; +using System.Net; using MediaBrowser.Model.Dto; namespace MediaBrowser.Model.Services { + // Remove this garbage class, it's just a bastard copy of NameValueCollection public class QueryParamCollection : List { public QueryParamCollection() @@ -20,6 +23,30 @@ namespace MediaBrowser.Model.Services } } + // TODO remove this shit + public QueryParamCollection(WebHeaderCollection webHeaderCollection) + { + foreach (var key in webHeaderCollection.AllKeys) + { + foreach (var value in webHeaderCollection.GetValues(key) ?? Array.Empty()) + { + Add(key, value); + } + } + } + + // TODO remove this shit + public QueryParamCollection(NameValueCollection nameValueCollection) + { + foreach (var key in nameValueCollection.AllKeys) + { + foreach (var value in nameValueCollection.GetValues(key) ?? Array.Empty()) + { + Add(key, value); + } + } + } + private static StringComparison GetStringComparison() { return StringComparison.OrdinalIgnoreCase; @@ -50,6 +77,10 @@ namespace MediaBrowser.Model.Services /// public virtual void Add(string key, string value) { + if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase)) + { + return; + } Add(new NameValuePair(key, value)); } diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs index 2b3c67071c..697c5d5b70 100644 --- a/SocketHttpListener/Ext.cs +++ b/SocketHttpListener/Ext.cs @@ -6,7 +6,6 @@ using System.Net; using System.Text; using System.Threading.Tasks; using MediaBrowser.Model.Services; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; using WebSocketState = System.Net.WebSockets.WebSocketState; namespace SocketHttpListener diff --git a/SocketHttpListener/HttpResponse.cs b/SocketHttpListener/HttpResponse.cs index d5d9b4a1c8..a33b942959 100644 --- a/SocketHttpListener/HttpResponse.cs +++ b/SocketHttpListener/HttpResponse.cs @@ -1,123 +1,122 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; -using HttpVersion = SocketHttpListener.Net.HttpVersion; - -namespace SocketHttpListener +using System; +using System.Linq; +using System.Net; +using System.Text; +using MediaBrowser.Model.Services; +using SocketHttpListener.Net; + +namespace SocketHttpListener { - internal class HttpResponse : HttpBase - { - #region Private Fields - - private string _code; - private string _reason; - - #endregion - - #region Private Constructors - - private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) - : base(version, headers) - { - _code = code; - _reason = reason; - } - - #endregion - - #region Internal Constructors - - internal HttpResponse(HttpStatusCode code) - : this(code, code.GetDescription()) - { - } - - internal HttpResponse(HttpStatusCode code, string reason) - : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) - { - Headers["Server"] = "websocket-sharp/1.0"; - } - - #endregion - - #region Public Properties - - public CookieCollection Cookies => GetCookies(Headers, true); - - private static CookieCollection GetCookies(QueryParamCollection headers, bool response) - { - var name = response ? "Set-Cookie" : "Cookie"; - return headers == null || !headers.Contains(name) - ? new CookieCollection() - : CookieHelper.Parse(headers[name], response); - } - - public bool IsProxyAuthenticationRequired => _code == "407"; - - public bool IsUnauthorized => _code == "401"; - - public bool IsWebSocketResponse - { - get - { - var headers = Headers; - return ProtocolVersion > HttpVersion.Version10 && - _code == "101" && - headers.Contains("Upgrade", "websocket") && - headers.Contains("Connection", "Upgrade"); - } - } - - public string Reason => _reason; - - public string StatusCode => _code; - - #endregion - - #region Internal Methods - - internal static HttpResponse CreateCloseResponse(HttpStatusCode code) - { - var res = new HttpResponse(code); - res.Headers["Connection"] = "close"; - - return res; - } - - #endregion - - #region Public Methods - - public void SetCookies(CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var headers = Headers; - var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); - - foreach (var cookie in sorted) - headers.Add("Set-Cookie", cookie.ToString()); - } - - public override string ToString() - { - var output = new StringBuilder(64); - output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); - - var headers = Headers; - foreach (var key in headers.Keys) - output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append(CrLf); - - return output.ToString(); - } - - #endregion - } -} + // TODO what is the point of this class? + internal class HttpResponse : HttpBase + { + #region Private Fields + + private string _code; + private string _reason; + + #endregion + + #region Private Constructors + + private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) + : base(version, headers) + { + _code = code; + _reason = reason; + } + + #endregion + + #region Internal Constructors + + internal HttpResponse(HttpStatusCode code) + : this(code, code.GetDescription()) + { + } + + internal HttpResponse(HttpStatusCode code, string reason) + : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) + { + Headers["Server"] = "websocket-sharp/1.0"; + } + + #endregion + + #region Public Properties + + public CookieCollection Cookies => GetCookies(Headers, true); + + private static CookieCollection GetCookies(QueryParamCollection headers, bool response) + { + var name = response ? "Set-Cookie" : "Cookie"; + return headers == null || !headers.Contains(name) + ? new CookieCollection() + : CookieHelper.Parse(headers[name], response); + } + + public bool IsProxyAuthenticationRequired => _code == "407"; + + public bool IsUnauthorized => _code == "401"; + + public bool IsWebSocketResponse + { + get + { + var headers = Headers; + return ProtocolVersion > HttpVersion.Version10 && + _code == "101" && + headers.Contains("Upgrade", "websocket") && + headers.Contains("Connection", "Upgrade"); + } + } + + public string Reason => _reason; + + public string StatusCode => _code; + + #endregion + + #region Internal Methods + + internal static HttpResponse CreateCloseResponse(HttpStatusCode code) + { + var res = new HttpResponse(code); + res.Headers["Connection"] = "close"; + + return res; + } + + #endregion + + #region Public Methods + + public void SetCookies(CookieCollection cookies) + { + if (cookies == null || cookies.Count == 0) + return; + + var headers = Headers; + var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); + + foreach (var cookie in sorted) + headers.Add("Set-Cookie", cookie.ToString()); + } + + public override string ToString() + { + var output = new StringBuilder(64); + output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); + + var headers = Headers; + foreach (var key in headers.Keys) + output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); + + output.Append(CrLf); + + return output.ToString(); + } + + #endregion + } +} diff --git a/SocketHttpListener/Net/AuthenticationSchemeSelector.cs b/SocketHttpListener/Net/AuthenticationSchemeSelector.cs deleted file mode 100644 index 77e6d0b8ab..0000000000 --- a/SocketHttpListener/Net/AuthenticationSchemeSelector.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Net; - -namespace SocketHttpListener.Net -{ - public delegate AuthenticationSchemes AuthenticationSchemeSelector(HttpListenerRequest httpRequest); -} diff --git a/SocketHttpListener/Net/AuthenticationTypes.cs b/SocketHttpListener/Net/AuthenticationTypes.cs deleted file mode 100644 index a3dbe9dfff..0000000000 --- a/SocketHttpListener/Net/AuthenticationTypes.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal class AuthenticationTypes - { - internal const string NTLM = "NTLM"; - internal const string Negotiate = "Negotiate"; - internal const string Basic = "Basic"; - } -} diff --git a/SocketHttpListener/Net/BoundaryType.cs b/SocketHttpListener/Net/BoundaryType.cs deleted file mode 100644 index da0b22910e..0000000000 --- a/SocketHttpListener/Net/BoundaryType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal enum BoundaryType - { - ContentLength = 0, // Content-Length: XXX - Chunked = 1, // Transfer-Encoding: chunked - Multipart = 3, - None = 4, - Invalid = 5, - } -} diff --git a/SocketHttpListener/Net/ChunkStream.cs b/SocketHttpListener/Net/ChunkStream.cs deleted file mode 100644 index 3836947d4c..0000000000 --- a/SocketHttpListener/Net/ChunkStream.cs +++ /dev/null @@ -1,385 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal sealed class ChunkStream - { - private enum State - { - None, - PartialSize, - Body, - BodyFinished, - Trailer - } - - private class Chunk - { - public byte[] Bytes; - public int Offset; - - public Chunk(byte[] chunk) - { - Bytes = chunk; - } - - public int Read(byte[] buffer, int offset, int size) - { - int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size; - Buffer.BlockCopy(Bytes, Offset, buffer, offset, nread); - Offset += nread; - return nread; - } - } - - internal WebHeaderCollection _headers; - private int _chunkSize; - private int _chunkRead; - private int _totalWritten; - private State _state; - private StringBuilder _saved; - private bool _sawCR; - private bool _gotit; - private int _trailerState; - private List _chunks; - - public ChunkStream(WebHeaderCollection headers) - { - _headers = headers; - _saved = new StringBuilder(); - _chunks = new List(); - _chunkSize = -1; - _totalWritten = 0; - } - - public void ResetBuffer() - { - _chunkSize = -1; - _chunkRead = 0; - _totalWritten = 0; - _chunks.Clear(); - } - - public int Read(byte[] buffer, int offset, int size) - { - return ReadFromChunks(buffer, offset, size); - } - - private int ReadFromChunks(byte[] buffer, int offset, int size) - { - int count = _chunks.Count; - int nread = 0; - - var chunksForRemoving = new List(count); - for (int i = 0; i < count; i++) - { - var chunk = _chunks[i]; - - if (chunk.Offset == chunk.Bytes.Length) - { - chunksForRemoving.Add(chunk); - continue; - } - - nread += chunk.Read(buffer, offset + nread, size - nread); - if (nread == size) - break; - } - - foreach (var chunk in chunksForRemoving) - _chunks.Remove(chunk); - - return nread; - } - - public void Write(byte[] buffer, int offset, int size) - { - // Note, the logic here only works when offset is 0 here. - // Otherwise, it would treat "size" as the end offset instead of an actual byte count from offset. - - if (offset < size) - InternalWrite(buffer, ref offset, size); - } - - private void InternalWrite(byte[] buffer, ref int offset, int size) - { - if (_state == State.None || _state == State.PartialSize) - { - _state = GetChunkSize(buffer, ref offset, size); - if (_state == State.PartialSize) - return; - - _saved.Length = 0; - _sawCR = false; - _gotit = false; - } - - if (_state == State.Body && offset < size) - { - _state = ReadBody(buffer, ref offset, size); - if (_state == State.Body) - return; - } - - if (_state == State.BodyFinished && offset < size) - { - _state = ReadCRLF(buffer, ref offset, size); - if (_state == State.BodyFinished) - return; - - _sawCR = false; - } - - if (_state == State.Trailer && offset < size) - { - _state = ReadTrailer(buffer, ref offset, size); - if (_state == State.Trailer) - return; - - _saved.Length = 0; - _sawCR = false; - _gotit = false; - } - - if (offset < size) - InternalWrite(buffer, ref offset, size); - } - - public bool WantMore => (_chunkRead != _chunkSize || _chunkSize != 0 || _state != State.None); - - public bool DataAvailable - { - get - { - int count = _chunks.Count; - for (int i = 0; i < count; i++) - { - var ch = _chunks[i]; - if (ch == null || ch.Bytes == null) - continue; - if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length) - return (_state != State.Body); - } - return false; - } - } - - public int TotalDataSize => _totalWritten; - - public int ChunkLeft => _chunkSize - _chunkRead; - - private State ReadBody(byte[] buffer, ref int offset, int size) - { - if (_chunkSize == 0) - return State.BodyFinished; - - int diff = size - offset; - if (diff + _chunkRead > _chunkSize) - diff = _chunkSize - _chunkRead; - - byte[] chunk = new byte[diff]; - Buffer.BlockCopy(buffer, offset, chunk, 0, diff); - _chunks.Add(new Chunk(chunk)); - offset += diff; - _chunkRead += diff; - _totalWritten += diff; - - return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body; - } - - private State GetChunkSize(byte[] buffer, ref int offset, int size) - { - _chunkRead = 0; - _chunkSize = 0; - char c = '\0'; - while (offset < size) - { - c = (char)buffer[offset++]; - if (c == '\r') - { - if (_sawCR) - ThrowProtocolViolation("2 CR found"); - - _sawCR = true; - continue; - } - - if (_sawCR && c == '\n') - break; - - if (c == ' ') - _gotit = true; - - if (!_gotit) - _saved.Append(c); - - if (_saved.Length > 20) - ThrowProtocolViolation("chunk size too long."); - } - - if (!_sawCR || c != '\n') - { - if (offset < size) - ThrowProtocolViolation("Missing \\n"); - - try - { - if (_saved.Length > 0) - { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber); - } - } - catch (Exception) - { - ThrowProtocolViolation("Cannot parse chunk size."); - } - - return State.PartialSize; - } - - _chunkRead = 0; - try - { - _chunkSize = int.Parse(RemoveChunkExtension(_saved.ToString()), NumberStyles.HexNumber); - } - catch (Exception) - { - ThrowProtocolViolation("Cannot parse chunk size."); - } - - if (_chunkSize == 0) - { - _trailerState = 2; - return State.Trailer; - } - - return State.Body; - } - - private static string RemoveChunkExtension(string input) - { - int idx = input.IndexOf(';'); - if (idx == -1) - return input; - return input.Substring(0, idx); - } - - private State ReadCRLF(byte[] buffer, ref int offset, int size) - { - if (!_sawCR) - { - if ((char)buffer[offset++] != '\r') - ThrowProtocolViolation("Expecting \\r"); - - _sawCR = true; - if (offset == size) - return State.BodyFinished; - } - - if (_sawCR && (char)buffer[offset++] != '\n') - ThrowProtocolViolation("Expecting \\n"); - - return State.None; - } - - private State ReadTrailer(byte[] buffer, ref int offset, int size) - { - char c = '\0'; - - // short path - if (_trailerState == 2 && (char)buffer[offset] == '\r' && _saved.Length == 0) - { - offset++; - if (offset < size && (char)buffer[offset] == '\n') - { - offset++; - return State.None; - } - offset--; - } - - int st = _trailerState; - string stString = "\r\n\r"; - while (offset < size && st < 4) - { - c = (char)buffer[offset++]; - if ((st == 0 || st == 2) && c == '\r') - { - st++; - continue; - } - - if ((st == 1 || st == 3) && c == '\n') - { - st++; - continue; - } - - if (st > 0) - { - _saved.Append(stString.Substring(0, _saved.Length == 0 ? st - 2 : st)); - st = 0; - if (_saved.Length > 4196) - ThrowProtocolViolation("Error reading trailer (too long)."); - } - } - - if (st < 4) - { - _trailerState = st; - if (offset < size) - ThrowProtocolViolation("Error reading trailer."); - - return State.Trailer; - } - - var reader = new StringReader(_saved.ToString()); - string line; - while ((line = reader.ReadLine()) != null && line != "") - _headers.Add(line); - - return State.None; - } - - private static void ThrowProtocolViolation(string message) - { - var we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null); - throw we; - } - } -} diff --git a/SocketHttpListener/Net/ChunkedInputStream.cs b/SocketHttpListener/Net/ChunkedInputStream.cs deleted file mode 100644 index 7aa8529e38..0000000000 --- a/SocketHttpListener/Net/ChunkedInputStream.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.IO; -using System.Net; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal sealed class ChunkedInputStream : HttpRequestStream - { - private ChunkStream _decoder; - private readonly HttpListenerContext _context; - private bool _no_more_data; - - private class ReadBufferState - { - public byte[] Buffer; - public int Offset; - public int Count; - public int InitialCount; - public HttpStreamAsyncResult Ares; - public ReadBufferState(byte[] buffer, int offset, int count, HttpStreamAsyncResult ares) - { - Buffer = buffer; - Offset = offset; - Count = count; - InitialCount = count; - Ares = ares; - } - } - - public ChunkedInputStream(HttpListenerContext context, Stream stream, byte[] buffer, int offset, int length) - : base(stream, buffer, offset, length) - { - _context = context; - var coll = (WebHeaderCollection)context.Request.Headers; - _decoder = new ChunkStream(coll); - } - - public ChunkStream Decoder - { - get => _decoder; - set => _decoder = value; - } - - protected override int ReadCore(byte[] buffer, int offset, int count) - { - IAsyncResult ares = BeginReadCore(buffer, offset, count, null, null); - return EndRead(ares); - } - - protected override IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - if (_no_more_data || size == 0 || _closed) - { - ares.Complete(); - return ares; - } - int nread = _decoder.Read(buffer, offset, size); - offset += nread; - size -= nread; - if (size == 0) - { - // got all we wanted, no need to bother the decoder yet - ares._count = nread; - ares.Complete(); - return ares; - } - if (!_decoder.WantMore) - { - _no_more_data = nread == 0; - ares._count = nread; - ares.Complete(); - return ares; - } - ares._buffer = new byte[8192]; - ares._offset = 0; - ares._count = 8192; - var rb = new ReadBufferState(buffer, offset, size, ares); - rb.InitialCount += nread; - base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb); - return ares; - } - - private void OnRead(IAsyncResult base_ares) - { - ReadBufferState rb = (ReadBufferState)base_ares.AsyncState; - var ares = rb.Ares; - try - { - int nread = base.EndRead(base_ares); - if (nread == 0) - { - _no_more_data = true; - ares._count = rb.InitialCount - rb.Count; - ares.Complete(); - return; - } - - _decoder.Write(ares._buffer, ares._offset, nread); - nread = _decoder.Read(rb.Buffer, rb.Offset, rb.Count); - rb.Offset += nread; - rb.Count -= nread; - if (rb.Count == 0 || !_decoder.WantMore) - { - _no_more_data = !_decoder.WantMore && nread == 0; - ares._count = rb.InitialCount - rb.Count; - ares.Complete(); - return; - } - ares._offset = 0; - ares._count = Math.Min(8192, _decoder.ChunkLeft + 6); - base.BeginReadCore(ares._buffer, ares._offset, ares._count, OnRead, rb); - } - catch (Exception e) - { - _context.Connection.SendError(e.Message, 400); - ares.Complete(e); - } - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - var ares = asyncResult as HttpStreamAsyncResult; - if (ares == null || !ReferenceEquals(this, ares._parent)) - { - throw new ArgumentException("Invalid async result"); - } - if (ares._endCalled) - { - throw new InvalidOperationException("Invalid end call"); - } - ares._endCalled = true; - - if (!asyncResult.IsCompleted) - asyncResult.AsyncWaitHandle.WaitOne(); - - if (ares._error != null) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "Operation aborted"); - - return ares._count; - } - } -} diff --git a/SocketHttpListener/Net/EntitySendFormat.cs b/SocketHttpListener/Net/EntitySendFormat.cs deleted file mode 100644 index 3ed981084d..0000000000 --- a/SocketHttpListener/Net/EntitySendFormat.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal enum EntitySendFormat - { - ContentLength = 0, // Content-Length: XXX - Chunked = 1, // Transfer-Encoding: chunked - } -} diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs deleted file mode 100644 index e89f4ed9b1..0000000000 --- a/SocketHttpListener/Net/HttpConnection.cs +++ /dev/null @@ -1,528 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; -namespace SocketHttpListener.Net -{ - sealed class HttpConnection - { - private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); - const int BufferSize = 8192; - Socket _socket; - Stream _stream; - HttpEndPointListener _epl; - MemoryStream _memoryStream; - byte[] _buffer; - HttpListenerContext _context; - StringBuilder _currentLine; - ListenerPrefix _prefix; - HttpRequestStream _requestStream; - HttpResponseStream _responseStream; - bool _chunked; - int _reuses; - bool _contextBound; - bool secure; - IPEndPoint local_ep; - HttpListener _lastListener; - X509Certificate cert; - SslStream ssl_stream; - - private readonly ILogger _logger; - private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, - X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem, - IEnvironmentInfo environment) - { - _logger = logger; - this._socket = socket; - this._epl = epl; - this.secure = secure; - this.cert = cert; - _cryptoProvider = cryptoProvider; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - if (secure == false) - { - _stream = new SocketStream(_socket, false); - } - else - { - ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) => - { - if (c == null) - { - return true; - } - - //var c2 = c as X509Certificate2; - //if (c2 == null) - //{ - // c2 = new X509Certificate2(c.GetRawCertData()); - //} - - //_clientCert = c2; - //_clientCertErrors = new int[] { (int)e }; - return true; - }); - - _stream = ssl_stream; - } - } - - public Stream Stream => _stream; - - public async Task Init() - { - if (ssl_stream != null) - { - var enableAsync = true; - if (enableAsync) - { - await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false); - } - else - { - ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); - } - } - - InitInternal(); - } - - private void InitInternal() - { - _contextBound = false; - _requestStream = null; - _responseStream = null; - _prefix = null; - _chunked = false; - _memoryStream = new MemoryStream(); - _position = 0; - _inputState = InputState.RequestLine; - _lineState = LineState.None; - _context = new HttpListenerContext(this); - } - - public bool IsClosed => (_socket == null); - - public int Reuses => _reuses; - - public IPEndPoint LocalEndPoint - { - get - { - if (local_ep != null) - return local_ep; - - local_ep = (IPEndPoint)_socket.LocalEndPoint; - return local_ep; - } - } - - public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; - - public bool IsSecure => secure; - - public ListenerPrefix Prefix - { - get => _prefix; - set => _prefix = value; - } - - private void OnTimeout(object unused) - { - //_logger.LogInformation("HttpConnection timer fired"); - CloseSocket(); - Unbind(); - } - - public void BeginReadRequest() - { - if (_buffer == null) - _buffer = new byte[BufferSize]; - try - { - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - catch - { - CloseSocket(); - Unbind(); - } - } - - public HttpRequestStream GetRequestStream(bool chunked, long contentlength) - { - if (_requestStream == null) - { - byte[] buffer = _memoryStream.GetBuffer(); - int length = (int)_memoryStream.Length; - _memoryStream = null; - if (chunked) - { - _chunked = true; - //_context.Response.SendChunked = true; - _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position); - } - else - { - _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength); - } - } - return _requestStream; - } - - public HttpResponseStream GetResponseStream(bool isExpect100Continue = false) - { - // TODO: can we get this _stream before reading the input? - if (_responseStream == null) - { - var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure; - - _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger); - } - return _responseStream; - } - - private static void OnRead(IAsyncResult ares) - { - var cnc = (HttpConnection)ares.AsyncState; - cnc.OnReadInternal(ares); - } - - private void OnReadInternal(IAsyncResult ares) - { - int nread = -1; - try - { - nread = _stream.EndRead(ares); - _memoryStream.Write(_buffer, 0, nread); - if (_memoryStream.Length > 32768) - { - SendError("Bad Request", 400); - Close(true); - return; - } - } - catch - { - if (_memoryStream != null && _memoryStream.Length > 0) - SendError(); - if (_socket != null) - { - CloseSocket(); - Unbind(); - } - return; - } - - if (nread == 0) - { - CloseSocket(); - Unbind(); - return; - } - - if (ProcessInput(_memoryStream)) - { - if (!_context.HaveError) - _context.Request.FinishInitialization(); - - if (_context.HaveError) - { - SendError(); - Close(true); - return; - } - - if (!_epl.BindContext(_context)) - { - const int NotFoundErrorCode = 404; - SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode); - Close(true); - return; - } - HttpListener listener = _epl.Listener; - if (_lastListener != listener) - { - RemoveConnection(); - listener.AddConnection(this); - _lastListener = listener; - } - - _contextBound = true; - listener.RegisterContext(_context); - return; - } - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - - private void RemoveConnection() - { - if (_lastListener == null) - _epl.RemoveConnection(this); - else - _lastListener.RemoveConnection(this); - } - - private enum InputState - { - RequestLine, - Headers - } - - private enum LineState - { - None, - CR, - LF - } - - InputState _inputState = InputState.RequestLine; - LineState _lineState = LineState.None; - int _position; - - // true -> done processing - // false -> need more input - private bool ProcessInput(MemoryStream ms) - { - byte[] buffer = ms.GetBuffer(); - int len = (int)ms.Length; - int used = 0; - string line; - - while (true) - { - if (_context.HaveError) - return true; - - if (_position >= len) - break; - - try - { - line = ReadLine(buffer, _position, len - _position, ref used); - _position += used; - } - catch - { - _context.ErrorMessage = "Bad request"; - _context.ErrorStatus = 400; - return true; - } - - if (line == null) - break; - - if (line == "") - { - if (_inputState == InputState.RequestLine) - continue; - _currentLine = null; - ms = null; - return true; - } - - if (_inputState == InputState.RequestLine) - { - _context.Request.SetRequestLine(line); - _inputState = InputState.Headers; - } - else - { - try - { - _context.Request.AddHeader(line); - } - catch (Exception e) - { - _context.ErrorMessage = e.Message; - _context.ErrorStatus = 400; - return true; - } - } - } - - if (used == len) - { - ms.SetLength(0); - _position = 0; - } - return false; - } - - private string ReadLine(byte[] buffer, int offset, int len, ref int used) - { - if (_currentLine == null) - _currentLine = new StringBuilder(128); - int last = offset + len; - used = 0; - for (int i = offset; i < last && _lineState != LineState.LF; i++) - { - used++; - byte b = buffer[i]; - if (b == 13) - { - _lineState = LineState.CR; - } - else if (b == 10) - { - _lineState = LineState.LF; - } - else - { - _currentLine.Append((char)b); - } - } - - string result = null; - if (_lineState == LineState.LF) - { - _lineState = LineState.None; - result = _currentLine.ToString(); - _currentLine.Length = 0; - } - - return result; - } - - public void SendError(string msg, int status) - { - try - { - HttpListenerResponse response = _context.Response; - response.StatusCode = status; - response.ContentType = "text/html"; - string description = HttpStatusDescription.Get(status); - string str; - if (msg != null) - str = string.Format("

{0} ({1})

", description, msg); - else - str = string.Format("

{0}

", description); - - byte[] error = Encoding.UTF8.GetBytes(str); - response.Close(error, false); - } - catch - { - // response was already closed - } - } - - public void SendError() - { - SendError(_context.ErrorMessage, _context.ErrorStatus); - } - - private void Unbind() - { - if (_contextBound) - { - _epl.UnbindContext(_context); - _contextBound = false; - } - } - - public void Close() - { - Close(false); - } - - private void CloseSocket() - { - if (_socket == null) - return; - - try - { - _socket.Close(); - } - catch { } - finally - { - _socket = null; - } - - RemoveConnection(); - } - - internal void Close(bool force) - { - if (_socket != null) - { - Stream st = GetResponseStream(); - if (st != null) - st.Close(); - - _responseStream = null; - } - - if (_socket != null) - { - force |= !_context.Request.KeepAlive; - if (!force) - force = (string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase)); - - if (!force && _context.Request.FlushInput()) - { - if (_chunked && _context.Response.ForceCloseChunked == false) - { - // Don't close. Keep working. - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - Socket s = _socket; - _socket = null; - try - { - if (s != null) - s.Shutdown(SocketShutdown.Both); - } - catch - { - } - finally - { - if (s != null) - { - try - { - s.Close(); - } - catch { } - } - } - Unbind(); - RemoveConnection(); - return; - } - } - } -} diff --git a/SocketHttpListener/Net/HttpEndPointListener.cs b/SocketHttpListener/Net/HttpEndPointListener.cs deleted file mode 100644 index 35d1af6486..0000000000 --- a/SocketHttpListener/Net/HttpEndPointListener.cs +++ /dev/null @@ -1,526 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Security.Cryptography.X509Certificates; -using System.Threading; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - internal sealed class HttpEndPointListener - { - private HttpListener _listener; - private IPEndPoint _endpoint; - private Socket _socket; - private Dictionary _prefixes; - private List _unhandledPrefixes; // host = '*' - private List _allPrefixes; // host = '+' - private X509Certificate _cert; - private bool _secure; - private Dictionary _unregisteredConnections; - - private readonly ILogger _logger; - private bool _closed; - private bool _enableDualMode; - private readonly ICryptoProvider _cryptoProvider; - private readonly ISocketFactory _socketFactory; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure, X509Certificate cert, - ILogger logger, ICryptoProvider cryptoProvider, ISocketFactory socketFactory, IStreamHelper streamHelper, - IFileSystem fileSystem, IEnvironmentInfo environment) - { - this._listener = listener; - _logger = logger; - _cryptoProvider = cryptoProvider; - _socketFactory = socketFactory; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - this._secure = secure; - this._cert = cert; - - _enableDualMode = addr.Equals(IPAddress.IPv6Any); - _endpoint = new IPEndPoint(addr, port); - - _prefixes = new Dictionary(); - _unregisteredConnections = new Dictionary(); - - CreateSocket(); - } - - internal HttpListener Listener => _listener; - - private void CreateSocket() - { - try - { - _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); - } - catch (SocketCreateException ex) - { - if (_enableDualMode && _endpoint.Address.Equals(IPAddress.IPv6Any) && - (string.Equals(ex.ErrorCode, "AddressFamilyNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono 4.8.1 and lower on bsd is throwing this - string.Equals(ex.ErrorCode, "ProtocolNotSupported", StringComparison.OrdinalIgnoreCase) || - // mono 5.2 on bsd is throwing this - string.Equals(ex.ErrorCode, "OperationNotSupported", StringComparison.OrdinalIgnoreCase))) - { - _endpoint = new IPEndPoint(IPAddress.Any, _endpoint.Port); - _enableDualMode = false; - _socket = CreateSocket(_endpoint.Address.AddressFamily, _enableDualMode); - } - else - { - throw; - } - } - - try - { - _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - } - catch (SocketException) - { - // This is not supported on all operating systems (qnap) - } - - _socket.Bind(_endpoint); - - // This is the number TcpListener uses. - _socket.Listen(2147483647); - - Accept(); - - _closed = false; - } - - private void Accept() - { - var acceptEventArg = new SocketAsyncEventArgs(); - acceptEventArg.UserToken = this; - acceptEventArg.Completed += new EventHandler(OnAccept); - - Accept(acceptEventArg); - } - - private static void TryCloseAndDispose(Socket socket) - { - try - { - using (socket) - { - socket.Close(); - } - } - catch - { - - } - } - - private static void TryClose(Socket socket) - { - try - { - socket.Close(); - } - catch - { - - } - } - - private void Accept(SocketAsyncEventArgs acceptEventArg) - { - // acceptSocket must be cleared since the context object is being reused - acceptEventArg.AcceptSocket = null; - - try - { - bool willRaiseEvent = _socket.AcceptAsync(acceptEventArg); - - if (!willRaiseEvent) - { - ProcessAccept(acceptEventArg); - } - } - catch (ObjectDisposedException) - { - // TODO Investigate or properly fix. - } - catch (Exception ex) - { - var epl = (HttpEndPointListener)acceptEventArg.UserToken; - - epl._logger.LogError(ex, "Error in socket.AcceptAsync"); - } - } - - // This method is the callback method associated with Socket.AcceptAsync - // operations and is invoked when an accept operation is complete - // - private static void OnAccept(object sender, SocketAsyncEventArgs e) - { - ProcessAccept(e); - } - - private static async void ProcessAccept(SocketAsyncEventArgs args) - { - var epl = (HttpEndPointListener)args.UserToken; - - if (epl._closed) - { - return; - } - - // http://msdn.microsoft.com/en-us/library/system.net.sockets.acceptSocket.acceptasync%28v=vs.110%29.aspx - // Under certain conditions ConnectionReset can occur - // Need to attept to re-accept - var socketError = args.SocketError; - var accepted = args.AcceptSocket; - - epl.Accept(args); - - if (socketError == SocketError.ConnectionReset) - { - epl._logger.LogError("SocketError.ConnectionReset reported. Attempting to re-accept."); - return; - } - - if (accepted == null) - { - return; - } - - if (epl._secure && epl._cert == null) - { - TryClose(accepted); - return; - } - - try - { - var remoteEndPointString = accepted.RemoteEndPoint == null ? string.Empty : accepted.RemoteEndPoint.ToString(); - var localEndPointString = accepted.LocalEndPoint == null ? string.Empty : accepted.LocalEndPoint.ToString(); - //_logger.LogInformation("HttpEndPointListener Accepting connection from {0} to {1} secure connection requested: {2}", remoteEndPointString, localEndPointString, _secure); - - var conn = new HttpConnection(epl._logger, accepted, epl, epl._secure, epl._cert, epl._cryptoProvider, epl._streamHelper, epl._fileSystem, epl._environment); - - await conn.Init().ConfigureAwait(false); - - //_logger.LogDebug("Adding unregistered connection to {0}. Id: {1}", accepted.RemoteEndPoint, connectionId); - lock (epl._unregisteredConnections) - { - epl._unregisteredConnections[conn] = conn; - } - conn.BeginReadRequest(); - } - catch (Exception ex) - { - epl._logger.LogError(ex, "Error in ProcessAccept"); - - TryClose(accepted); - epl.Accept(); - return; - } - } - - private Socket CreateSocket(AddressFamily addressFamily, bool dualMode) - { - try - { - var socket = new Socket(addressFamily, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - - if (dualMode) - { - socket.DualMode = true; - } - - return socket; - } - catch (SocketException ex) - { - throw new SocketCreateException(ex.SocketErrorCode.ToString(), ex); - } - catch (ArgumentException ex) - { - if (dualMode) - { - // Mono for BSD incorrectly throws ArgumentException instead of SocketException - throw new SocketCreateException("AddressFamilyNotSupported", ex); - } - else - { - throw; - } - } - } - - internal void RemoveConnection(HttpConnection conn) - { - lock (_unregisteredConnections) - { - _unregisteredConnections.Remove(conn); - } - } - - public bool BindContext(HttpListenerContext context) - { - var req = context.Request; - HttpListener listener = SearchListener(req.Url, out var prefix); - if (listener == null) - return false; - - context.Connection.Prefix = prefix; - return true; - } - - public void UnbindContext(HttpListenerContext context) - { - if (context == null || context.Request == null) - return; - - _listener.UnregisterContext(context); - } - - private HttpListener SearchListener(Uri uri, out ListenerPrefix prefix) - { - prefix = null; - if (uri == null) - return null; - - string host = uri.Host; - int port = uri.Port; - string path = WebUtility.UrlDecode(uri.AbsolutePath); - string pathSlash = path[path.Length - 1] == '/' ? path : path + "/"; - - HttpListener bestMatch = null; - int bestLength = -1; - - if (host != null && host != "") - { - Dictionary localPrefixes = _prefixes; - foreach (var p in localPrefixes.Keys) - { - string ppath = p.Path; - if (ppath.Length < bestLength) - continue; - - if (p.Host != host || p.Port != port) - continue; - - if (path.StartsWith(ppath) || pathSlash.StartsWith(ppath)) - { - bestLength = ppath.Length; - bestMatch = localPrefixes[p]; - prefix = p; - } - } - if (bestLength != -1) - return bestMatch; - } - - List list = _unhandledPrefixes; - bestMatch = MatchFromList(host, path, list, out prefix); - - if (path != pathSlash && bestMatch == null) - bestMatch = MatchFromList(host, pathSlash, list, out prefix); - - if (bestMatch != null) - return bestMatch; - - list = _allPrefixes; - bestMatch = MatchFromList(host, path, list, out prefix); - - if (path != pathSlash && bestMatch == null) - bestMatch = MatchFromList(host, pathSlash, list, out prefix); - - if (bestMatch != null) - return bestMatch; - - return null; - } - - private HttpListener MatchFromList(string host, string path, List list, out ListenerPrefix prefix) - { - prefix = null; - if (list == null) - return null; - - HttpListener bestMatch = null; - int bestLength = -1; - - foreach (ListenerPrefix p in list) - { - string ppath = p.Path; - if (ppath.Length < bestLength) - continue; - - if (path.StartsWith(ppath)) - { - bestLength = ppath.Length; - bestMatch = p._listener; - prefix = p; - } - } - - return bestMatch; - } - - private void AddSpecial(List list, ListenerPrefix prefix) - { - if (list == null) - return; - - foreach (ListenerPrefix p in list) - { - if (p.Path == prefix.Path) - throw new Exception("net_listener_already"); - } - list.Add(prefix); - } - - private bool RemoveSpecial(List list, ListenerPrefix prefix) - { - if (list == null) - return false; - - int c = list.Count; - for (int i = 0; i < c; i++) - { - ListenerPrefix p = list[i]; - if (p.Path == prefix.Path) - { - list.RemoveAt(i); - return true; - } - } - return false; - } - - private void CheckIfRemove() - { - if (_prefixes.Count > 0) - return; - - List list = _unhandledPrefixes; - if (list != null && list.Count > 0) - return; - - list = _allPrefixes; - if (list != null && list.Count > 0) - return; - - HttpEndPointManager.RemoveEndPoint(this, _endpoint); - } - - public void Close() - { - _closed = true; - _socket.Close(); - lock (_unregisteredConnections) - { - // Clone the list because RemoveConnection can be called from Close - var connections = new List(_unregisteredConnections.Keys); - - foreach (HttpConnection c in connections) - c.Close(true); - _unregisteredConnections.Clear(); - } - } - - public void AddPrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = _unhandledPrefixes; - future = current != null ? new List(current) : new List(); - prefix._listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); - return; - } - - if (prefix.Host == "+") - { - do - { - current = _allPrefixes; - future = current != null ? new List(current) : new List(); - prefix._listener = listener; - AddSpecial(future, prefix); - } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); - return; - } - - Dictionary prefs, p2; - do - { - prefs = _prefixes; - if (prefs.ContainsKey(prefix)) - { - throw new Exception("net_listener_already"); - } - p2 = new Dictionary(prefs); - p2[prefix] = listener; - } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); - } - - public void RemovePrefix(ListenerPrefix prefix, HttpListener listener) - { - List current; - List future; - if (prefix.Host == "*") - { - do - { - current = _unhandledPrefixes; - future = current != null ? new List(current) : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current); - - CheckIfRemove(); - return; - } - - if (prefix.Host == "+") - { - do - { - current = _allPrefixes; - future = current != null ? new List(current) : new List(); - if (!RemoveSpecial(future, prefix)) - break; // Prefix not found - } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current); - CheckIfRemove(); - return; - } - - Dictionary prefs, p2; - do - { - prefs = _prefixes; - if (!prefs.ContainsKey(prefix)) - break; - - p2 = new Dictionary(prefs); - p2.Remove(prefix); - } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs); - CheckIfRemove(); - } - } -} diff --git a/SocketHttpListener/Net/HttpEndPointManager.cs b/SocketHttpListener/Net/HttpEndPointManager.cs deleted file mode 100644 index 27b4244a6c..0000000000 --- a/SocketHttpListener/Net/HttpEndPointManager.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - internal sealed class HttpEndPointManager - { - private static Dictionary> s_ipEndPoints = new Dictionary>(); - - private HttpEndPointManager() - { - } - - public static void AddListener(ILogger logger, HttpListener listener) - { - var added = new List(); - try - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - foreach (string prefix in listener.Prefixes) - { - AddPrefixInternal(logger, prefix, listener); - added.Add(prefix); - } - } - } - catch - { - foreach (string prefix in added) - { - RemovePrefix(logger, prefix, listener); - } - throw; - } - } - - public static void AddPrefix(ILogger logger, string prefix, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - AddPrefixInternal(logger, prefix, listener); - } - } - - private static void AddPrefixInternal(ILogger logger, string p, HttpListener listener) - { - int start = p.IndexOf(':') + 3; - int colon = p.IndexOf(':', start); - if (colon != -1) - { - // root can't be -1 here, since we've already checked for ending '/' in ListenerPrefix. - int root = p.IndexOf('/', colon, p.Length - colon); - string portString = p.Substring(colon + 1, root - colon - 1); - - if (!int.TryParse(portString, out var port) || port <= 0 || port >= 65536) - { - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_port"); - } - } - - var lp = new ListenerPrefix(p); - if (lp.Host != "*" && lp.Host != "+" && Uri.CheckHostName(lp.Host) == UriHostNameType.Unknown) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_listener_host"); - - if (lp.Path.IndexOf('%') != -1) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - throw new HttpListenerException((int)HttpStatusCode.BadRequest, "net_invalid_path"); - - // listens on all the interfaces if host name cannot be parsed by IPAddress. - HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); - epl.AddPrefix(lp, listener); - } - - private static IPAddress GetIpAnyAddress(HttpListener listener) - { - return listener.EnableDualMode ? IPAddress.IPv6Any : IPAddress.Any; - } - - private static HttpEndPointListener GetEPListener(ILogger logger, string host, int port, HttpListener listener, bool secure) - { - IPAddress addr; - if (host == "*" || host == "+") - { - addr = GetIpAnyAddress(listener); - } - else - { - const int NotSupportedErrorCode = 50; - try - { - addr = Dns.GetHostAddresses(host)[0]; - } - catch - { - // Throw same error code as windows, request is not supported. - throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); - } - - if (IPAddress.Any.Equals(addr)) - { - // Don't support listening to 0.0.0.0, match windows behavior. - throw new HttpListenerException(NotSupportedErrorCode, "net_listener_not_supported"); - } - } - - Dictionary p = null; - if (s_ipEndPoints.ContainsKey(addr)) - { - p = s_ipEndPoints[addr]; - } - else - { - p = new Dictionary(); - s_ipEndPoints[addr] = p; - } - - HttpEndPointListener epl = null; - if (p.ContainsKey(port)) - { - epl = p[port]; - } - else - { - try - { - epl = new HttpEndPointListener(listener, addr, port, secure, listener.Certificate, logger, listener.CryptoProvider, listener.SocketFactory, listener.StreamHelper, listener.FileSystem, listener.EnvironmentInfo); - } - catch (SocketException ex) - { - throw new HttpListenerException(ex.ErrorCode, ex.Message); - } - p[port] = epl; - } - - return epl; - } - - public static void RemoveEndPoint(HttpEndPointListener epl, IPEndPoint ep) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - Dictionary p = null; - p = s_ipEndPoints[ep.Address]; - p.Remove(ep.Port); - if (p.Count == 0) - { - s_ipEndPoints.Remove(ep.Address); - } - epl.Close(); - } - } - - public static void RemoveListener(ILogger logger, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - foreach (string prefix in listener.Prefixes) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - } - - public static void RemovePrefix(ILogger logger, string prefix, HttpListener listener) - { - lock ((s_ipEndPoints as ICollection).SyncRoot) - { - RemovePrefixInternal(logger, prefix, listener); - } - } - - private static void RemovePrefixInternal(ILogger logger, string prefix, HttpListener listener) - { - var lp = new ListenerPrefix(prefix); - if (lp.Path.IndexOf('%') != -1) - return; - - if (lp.Path.IndexOf("//", StringComparison.Ordinal) != -1) - return; - - HttpEndPointListener epl = GetEPListener(logger, lp.Host, lp.Port, listener, lp.Secure); - epl.RemovePrefix(lp, listener); - } - } -} diff --git a/SocketHttpListener/Net/HttpKnownHeaderNames.cs b/SocketHttpListener/Net/HttpKnownHeaderNames.cs deleted file mode 100644 index dc6f2ce415..0000000000 --- a/SocketHttpListener/Net/HttpKnownHeaderNames.cs +++ /dev/null @@ -1,91 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static partial class HttpKnownHeaderNames - { - // When adding a new constant, add it to HttpKnownHeaderNames.TryGetHeaderName.cs as well. - - public const string Accept = "Accept"; - public const string AcceptCharset = "Accept-Charset"; - public const string AcceptEncoding = "Accept-Encoding"; - public const string AcceptLanguage = "Accept-Language"; - public const string AcceptPatch = "Accept-Patch"; - public const string AcceptRanges = "Accept-Ranges"; - public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials"; - public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers"; - public const string AccessControlAllowMethods = "Access-Control-Allow-Methods"; - public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin"; - public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers"; - public const string AccessControlMaxAge = "Access-Control-Max-Age"; - public const string Age = "Age"; - public const string Allow = "Allow"; - public const string AltSvc = "Alt-Svc"; - public const string Authorization = "Authorization"; - public const string CacheControl = "Cache-Control"; - public const string Connection = "Connection"; - public const string ContentDisposition = "Content-Disposition"; - public const string ContentEncoding = "Content-Encoding"; - public const string ContentLanguage = "Content-Language"; - public const string ContentLength = "Content-Length"; - public const string ContentLocation = "Content-Location"; - public const string ContentMD5 = "Content-MD5"; - public const string ContentRange = "Content-Range"; - public const string ContentSecurityPolicy = "Content-Security-Policy"; - public const string ContentType = "Content-Type"; - public const string Cookie = "Cookie"; - public const string Cookie2 = "Cookie2"; - public const string Date = "Date"; - public const string ETag = "ETag"; - public const string Expect = "Expect"; - public const string Expires = "Expires"; - public const string From = "From"; - public const string Host = "Host"; - public const string IfMatch = "If-Match"; - public const string IfModifiedSince = "If-Modified-Since"; - public const string IfNoneMatch = "If-None-Match"; - public const string IfRange = "If-Range"; - public const string IfUnmodifiedSince = "If-Unmodified-Since"; - public const string KeepAlive = "Keep-Alive"; - public const string LastModified = "Last-Modified"; - public const string Link = "Link"; - public const string Location = "Location"; - public const string MaxForwards = "Max-Forwards"; - public const string Origin = "Origin"; - public const string P3P = "P3P"; - public const string Pragma = "Pragma"; - public const string ProxyAuthenticate = "Proxy-Authenticate"; - public const string ProxyAuthorization = "Proxy-Authorization"; - public const string ProxyConnection = "Proxy-Connection"; - public const string PublicKeyPins = "Public-Key-Pins"; - public const string Range = "Range"; - public const string Referer = "Referer"; // NB: The spelling-mistake "Referer" for "Referrer" must be matched. - public const string RetryAfter = "Retry-After"; - public const string SecWebSocketAccept = "Sec-WebSocket-Accept"; - public const string SecWebSocketExtensions = "Sec-WebSocket-Extensions"; - public const string SecWebSocketKey = "Sec-WebSocket-Key"; - public const string SecWebSocketProtocol = "Sec-WebSocket-Protocol"; - public const string SecWebSocketVersion = "Sec-WebSocket-Version"; - public const string Server = "Server"; - public const string SetCookie = "Set-Cookie"; - public const string SetCookie2 = "Set-Cookie2"; - public const string StrictTransportSecurity = "Strict-Transport-Security"; - public const string TE = "TE"; - public const string TSV = "TSV"; - public const string Trailer = "Trailer"; - public const string TransferEncoding = "Transfer-Encoding"; - public const string Upgrade = "Upgrade"; - public const string UpgradeInsecureRequests = "Upgrade-Insecure-Requests"; - public const string UserAgent = "User-Agent"; - public const string Vary = "Vary"; - public const string Via = "Via"; - public const string WWWAuthenticate = "WWW-Authenticate"; - public const string Warning = "Warning"; - public const string XAspNetVersion = "X-AspNet-Version"; - public const string XContentDuration = "X-Content-Duration"; - public const string XContentTypeOptions = "X-Content-Type-Options"; - public const string XFrameOptions = "X-Frame-Options"; - public const string XMSEdgeRef = "X-MSEdge-Ref"; - public const string XPoweredBy = "X-Powered-By"; - public const string XRequestID = "X-Request-ID"; - public const string XUACompatible = "X-UA-Compatible"; - } -} diff --git a/SocketHttpListener/Net/HttpListener.cs b/SocketHttpListener/Net/HttpListener.cs deleted file mode 100644 index f17036a21a..0000000000 --- a/SocketHttpListener/Net/HttpListener.cs +++ /dev/null @@ -1,284 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - public sealed class HttpListener : IDisposable - { - internal ICryptoProvider CryptoProvider { get; private set; } - internal ISocketFactory SocketFactory { get; private set; } - internal IFileSystem FileSystem { get; private set; } - internal IStreamHelper StreamHelper { get; private set; } - internal IEnvironmentInfo EnvironmentInfo { get; private set; } - - public bool EnableDualMode { get; set; } - - private AuthenticationSchemes auth_schemes; - private HttpListenerPrefixCollection prefixes; - private AuthenticationSchemeSelector auth_selector; - private string realm; - private bool unsafe_ntlm_auth; - private bool listening; - private bool disposed; - - private Dictionary registry; - private Dictionary connections; - private ILogger _logger; - private X509Certificate _certificate; - - public Action OnContext { get; set; } - - public HttpListener( - ILogger logger, - ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, - IStreamHelper streamHelper, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) - { - _logger = logger; - CryptoProvider = cryptoProvider; - SocketFactory = socketFactory; - StreamHelper = streamHelper; - FileSystem = fileSystem; - EnvironmentInfo = environmentInfo; - - prefixes = new HttpListenerPrefixCollection(logger, this); - registry = new Dictionary(); - connections = new Dictionary(); - auth_schemes = AuthenticationSchemes.Anonymous; - } - - public HttpListener( - ILogger logger, - X509Certificate certificate, - ICryptoProvider cryptoProvider, - ISocketFactory socketFactory, - IStreamHelper streamHelper, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) - : this(logger, cryptoProvider, socketFactory, streamHelper, fileSystem, environmentInfo) - { - _certificate = certificate; - } - - public void LoadCert(X509Certificate cert) - { - _certificate = cert; - } - - // TODO: Digest, NTLM and Negotiate require ControlPrincipal - public AuthenticationSchemes AuthenticationSchemes - { - get => auth_schemes; - set - { - CheckDisposed(); - auth_schemes = value; - } - } - - public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate - { - get => auth_selector; - set - { - CheckDisposed(); - auth_selector = value; - } - } - - public bool IsListening => listening; - - public static bool IsSupported => true; - - public HttpListenerPrefixCollection Prefixes - { - get - { - CheckDisposed(); - return prefixes; - } - } - - // TODO: use this - public string Realm - { - get => realm; - set - { - CheckDisposed(); - realm = value; - } - } - - public bool UnsafeConnectionNtlmAuthentication - { - get => unsafe_ntlm_auth; - set - { - CheckDisposed(); - unsafe_ntlm_auth = value; - } - } - - //internal IMonoSslStream CreateSslStream(Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback) - //{ - // lock (registry) - // { - // if (tlsProvider == null) - // tlsProvider = MonoTlsProviderFactory.GetProviderInternal(); - // if (tlsSettings == null) - // tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings(); - // if (tlsSettings.RemoteCertificateValidationCallback == null) - // tlsSettings.RemoteCertificateValidationCallback = callback; - // return tlsProvider.CreateSslStream(innerStream, ownsStream, tlsSettings); - // } - //} - - internal X509Certificate Certificate => _certificate; - - public void Abort() - { - if (disposed) - return; - - if (!listening) - { - return; - } - - Close(true); - } - - public void Close() - { - if (disposed) - return; - - if (!listening) - { - disposed = true; - return; - } - - Close(true); - disposed = true; - } - - void Close(bool force) - { - CheckDisposed(); - HttpEndPointManager.RemoveListener(_logger, this); - Cleanup(force); - } - - void Cleanup(bool close_existing) - { - lock (registry) - { - if (close_existing) - { - // Need to copy this since closing will call UnregisterContext - ICollection keys = registry.Keys; - var all = new HttpListenerContext[keys.Count]; - keys.CopyTo(all, 0); - registry.Clear(); - for (int i = all.Length - 1; i >= 0; i--) - all[i].Connection.Close(true); - } - - lock (connections) - { - ICollection keys = connections.Keys; - var conns = new HttpConnection[keys.Count]; - keys.CopyTo(conns, 0); - connections.Clear(); - for (int i = conns.Length - 1; i >= 0; i--) - conns[i].Close(true); - } - } - } - - internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context) - { - if (AuthenticationSchemeSelectorDelegate != null) - return AuthenticationSchemeSelectorDelegate(context.Request); - else - return auth_schemes; - } - - public void Start() - { - CheckDisposed(); - if (listening) - return; - - HttpEndPointManager.AddListener(_logger, this); - listening = true; - } - - public void Stop() - { - CheckDisposed(); - listening = false; - Close(false); - } - - void IDisposable.Dispose() - { - if (disposed) - return; - - Close(true); //TODO: Should we force here or not? - disposed = true; - } - - internal void CheckDisposed() - { - if (disposed) - throw new ObjectDisposedException(GetType().Name); - } - - internal void RegisterContext(HttpListenerContext context) - { - if (OnContext != null && IsListening) - { - OnContext(context); - } - - lock (registry) - registry[context] = context; - } - - internal void UnregisterContext(HttpListenerContext context) - { - lock (registry) - registry.Remove(context); - } - - internal void AddConnection(HttpConnection cnc) - { - lock (connections) - { - connections[cnc] = cnc; - } - } - - internal void RemoveConnection(HttpConnection cnc) - { - lock (connections) - { - connections.Remove(cnc); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerBasicIdentity.cs b/SocketHttpListener/Net/HttpListenerBasicIdentity.cs deleted file mode 100644 index 5f6ec44b96..0000000000 --- a/SocketHttpListener/Net/HttpListenerBasicIdentity.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Security.Principal; - -namespace SocketHttpListener.Net -{ - public class HttpListenerBasicIdentity : GenericIdentity - { - string password; - - public HttpListenerBasicIdentity(string username, string password) - : base(username, "Basic") - { - this.password = password; - } - - public virtual string Password => password; - } - - public class GenericIdentity : IIdentity - { - private string m_name; - private string m_type; - - public GenericIdentity(string name) - { - if (name == null) - throw new System.ArgumentNullException(nameof(name)); - - m_name = name; - m_type = ""; - } - - public GenericIdentity(string name, string type) - { - if (name == null) - throw new System.ArgumentNullException(nameof(name)); - if (type == null) - throw new System.ArgumentNullException(nameof(type)); - - m_name = name; - m_type = type; - } - - public virtual string Name => m_name; - - public virtual string AuthenticationType => m_type; - - public virtual bool IsAuthenticated => !m_name.Equals(""); - } -} diff --git a/SocketHttpListener/Net/HttpListenerContext.Managed.cs b/SocketHttpListener/Net/HttpListenerContext.Managed.cs deleted file mode 100644 index 79742bf37f..0000000000 --- a/SocketHttpListener/Net/HttpListenerContext.Managed.cs +++ /dev/null @@ -1,99 +0,0 @@ -using System; -using System.ComponentModel; -using System.Security.Principal; -using System.Text; -using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerContext - { - private HttpConnection _connection; - - internal HttpListenerContext(HttpConnection connection) - { - _connection = connection; - _response = new HttpListenerResponse(this); - Request = new HttpListenerRequest(this); - ErrorStatus = 400; - } - - internal int ErrorStatus { get; set; } - - internal string ErrorMessage { get; set; } - - internal bool HaveError => ErrorMessage != null; - - internal HttpConnection Connection => _connection; - - internal void ParseAuthentication(System.Net.AuthenticationSchemes expectedSchemes) - { - if (expectedSchemes == System.Net.AuthenticationSchemes.Anonymous) - return; - - string header = Request.Headers["Authorization"]; - if (string.IsNullOrEmpty(header)) - return; - - if (IsBasicHeader(header)) - { - _user = ParseBasicAuthentication(header.Substring(AuthenticationTypes.Basic.Length + 1)); - } - } - - internal IPrincipal ParseBasicAuthentication(string authData) => - TryParseBasicAuth(authData, out HttpStatusCode errorCode, out string username, out string password) ? - new GenericPrincipal(new HttpListenerBasicIdentity(username, password), Array.Empty()) : - null; - - internal static bool IsBasicHeader(string header) => - header.Length >= 6 && - header[5] == ' ' && - string.Compare(header, 0, AuthenticationTypes.Basic, 0, 5, StringComparison.OrdinalIgnoreCase) == 0; - - internal static bool TryParseBasicAuth(string headerValue, out HttpStatusCode errorCode, out string username, out string password) - { - errorCode = HttpStatusCode.OK; - username = password = null; - try - { - if (string.IsNullOrWhiteSpace(headerValue)) - { - return false; - } - - string authString = Encoding.UTF8.GetString(Convert.FromBase64String(headerValue)); - int colonPos = authString.IndexOf(':'); - if (colonPos < 0) - { - // username must be at least 1 char - errorCode = HttpStatusCode.BadRequest; - return false; - } - - username = authString.Substring(0, colonPos); - password = authString.Substring(colonPos + 1); - return true; - } - catch - { - errorCode = HttpStatusCode.InternalServerError; - return false; - } - } - - public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval) - { - return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval); - } - - [EditorBrowsable(EditorBrowsableState.Never)] - public Task AcceptWebSocketAsync(string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment internalBuffer) - { - WebSocketValidate.ValidateArraySegment(internalBuffer, nameof(internalBuffer)); - HttpWebSocket.ValidateOptions(subProtocol, receiveBufferSize, HttpWebSocket.MinSendBufferSize, keepAliveInterval); - return HttpWebSocket.AcceptWebSocketAsyncCore(this, subProtocol, receiveBufferSize, keepAliveInterval, internalBuffer); - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerContext.cs b/SocketHttpListener/Net/HttpListenerContext.cs deleted file mode 100644 index d84e2d1aa0..0000000000 --- a/SocketHttpListener/Net/HttpListenerContext.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Net; -using System.Security.Principal; -using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerContext - { - private HttpListenerResponse _response; - private IPrincipal _user; - - public HttpListenerRequest Request { get; } - - public IPrincipal User => _user; - - // This can be used to cache the results of HttpListener.AuthenticationSchemeSelectorDelegate. - internal AuthenticationSchemes AuthenticationSchemes { get; set; } - - public HttpListenerResponse Response => _response; - - public Task AcceptWebSocketAsync(string subProtocol) - { - return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, WebSocket.DefaultKeepAliveInterval); - } - - public Task AcceptWebSocketAsync(string subProtocol, TimeSpan keepAliveInterval) - { - return AcceptWebSocketAsync(subProtocol, HttpWebSocket.DefaultReceiveBufferSize, keepAliveInterval); - } - } - - public class GenericPrincipal : IPrincipal - { - private IIdentity m_identity; - private string[] m_roles; - - public GenericPrincipal(IIdentity identity, string[] roles) - { - if (identity == null) - throw new ArgumentNullException(nameof(identity)); - - m_identity = identity; - if (roles != null) - { - m_roles = new string[roles.Length]; - for (int i = 0; i < roles.Length; ++i) - { - m_roles[i] = roles[i]; - } - } - else - { - m_roles = null; - } - } - - public virtual IIdentity Identity => m_identity; - - public virtual bool IsInRole(string role) - { - if (role == null || m_roles == null) - return false; - - for (int i = 0; i < m_roles.Length; ++i) - { - if (m_roles[i] != null && string.Compare(m_roles[i], role, StringComparison.OrdinalIgnoreCase) == 0) - return true; - } - return false; - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs b/SocketHttpListener/Net/HttpListenerPrefixCollection.cs deleted file mode 100644 index 400a1adb61..0000000000 --- a/SocketHttpListener/Net/HttpListenerPrefixCollection.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - public class HttpListenerPrefixCollection : ICollection, IEnumerable, IEnumerable - { - private List _prefixes = new List(); - private HttpListener _listener; - - private ILogger _logger; - - internal HttpListenerPrefixCollection(ILogger logger, HttpListener listener) - { - _logger = logger; - _listener = listener; - } - - public int Count => _prefixes.Count; - - public bool IsReadOnly => false; - - public bool IsSynchronized => false; - - public void Add(string uriPrefix) - { - _listener.CheckDisposed(); - //ListenerPrefix.CheckUri(uriPrefix); - if (_prefixes.Contains(uriPrefix)) - { - return; - } - - _prefixes.Add(uriPrefix); - if (_listener.IsListening) - { - HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); - } - } - - public void AddRange(IEnumerable uriPrefixes) - { - _listener.CheckDisposed(); - - foreach (var uriPrefix in uriPrefixes) - { - if (_prefixes.Contains(uriPrefix)) - { - continue; - } - - _prefixes.Add(uriPrefix); - if (_listener.IsListening) - { - HttpEndPointManager.AddPrefix(_logger, uriPrefix, _listener); - } - } - } - - public void Clear() - { - _listener.CheckDisposed(); - _prefixes.Clear(); - if (_listener.IsListening) - { - HttpEndPointManager.RemoveListener(_logger, _listener); - } - } - - public bool Contains(string uriPrefix) - { - _listener.CheckDisposed(); - return _prefixes.Contains(uriPrefix); - } - - public void CopyTo(string[] array, int offset) - { - _listener.CheckDisposed(); - _prefixes.CopyTo(array, offset); - } - - public void CopyTo(Array array, int offset) - { - _listener.CheckDisposed(); - ((ICollection)_prefixes).CopyTo(array, offset); - } - - public IEnumerator GetEnumerator() - { - return _prefixes.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _prefixes.GetEnumerator(); - } - - public bool Remove(string uriPrefix) - { - _listener.CheckDisposed(); - if (uriPrefix == null) - { - throw new ArgumentNullException(nameof(uriPrefix)); - } - - bool result = _prefixes.Remove(uriPrefix); - if (result && _listener.IsListening) - { - HttpEndPointManager.RemovePrefix(_logger, uriPrefix, _listener); - } - - return result; - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs b/SocketHttpListener/Net/HttpListenerRequest.Managed.cs deleted file mode 100644 index 3f9e32f089..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequest.Managed.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.IO; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerRequest - { - private long _contentLength; - private bool _clSet; - private WebHeaderCollection _headers; - private string _method; - private Stream _inputStream; - private HttpListenerContext _context; - private bool _isChunked; - - private static byte[] s_100continue = Encoding.ASCII.GetBytes("HTTP/1.1 100 Continue\r\n\r\n"); - - internal HttpListenerRequest(HttpListenerContext context) - { - _context = context; - _headers = new WebHeaderCollection(); - _version = HttpVersion.Version10; - } - - private static readonly char[] s_separators = new char[] { ' ' }; - - internal void SetRequestLine(string req) - { - string[] parts = req.Split(s_separators, 3); - if (parts.Length != 3) - { - _context.ErrorMessage = "Invalid request line (parts)."; - return; - } - - _method = parts[0]; - foreach (char c in _method) - { - int ic = (int)c; - - if ((ic >= 'A' && ic <= 'Z') || - (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && - c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && - c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && - c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) - continue; - - _context.ErrorMessage = "(Invalid verb)"; - return; - } - - _rawUrl = parts[1]; - if (parts[2].Length != 8 || !parts[2].StartsWith("HTTP/")) - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - - try - { - _version = new Version(parts[2].Substring(5)); - } - catch - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - - if (_version.Major < 1) - { - _context.ErrorMessage = "Invalid request line (version)."; - return; - } - if (_version.Major > 1) - { - _context.ErrorStatus = (int)HttpStatusCode.HttpVersionNotSupported; - _context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.HttpVersionNotSupported); - return; - } - } - - private static bool MaybeUri(string s) - { - int p = s.IndexOf(':'); - if (p == -1) - return false; - - if (p >= 10) - return false; - - return IsPredefinedScheme(s.Substring(0, p)); - } - - private static bool IsPredefinedScheme(string scheme) - { - if (scheme == null || scheme.Length < 3) - return false; - - char c = scheme[0]; - if (c == 'h') - return (scheme == UriScheme.Http || scheme == UriScheme.Https); - if (c == 'f') - return (scheme == UriScheme.File || scheme == UriScheme.Ftp); - - if (c == 'n') - { - c = scheme[1]; - if (c == 'e') - return (scheme == UriScheme.News || scheme == UriScheme.NetPipe || scheme == UriScheme.NetTcp); - if (scheme == UriScheme.Nntp) - return true; - return false; - } - if ((c == 'g' && scheme == UriScheme.Gopher) || (c == 'm' && scheme == UriScheme.Mailto)) - return true; - - return false; - } - - internal void FinishInitialization() - { - string host = UserHostName; - if (_version > HttpVersion.Version10 && (host == null || host.Length == 0)) - { - _context.ErrorMessage = "Invalid host name"; - return; - } - - string path; - Uri raw_uri = null; - if (MaybeUri(_rawUrl.ToLowerInvariant()) && Uri.TryCreate(_rawUrl, UriKind.Absolute, out raw_uri)) - path = raw_uri.PathAndQuery; - else - path = _rawUrl; - - if ((host == null || host.Length == 0)) - host = UserHostAddress; - - if (raw_uri != null) - host = raw_uri.Host; - - int colon = host.IndexOf(']') == -1 ? host.IndexOf(':') : host.LastIndexOf(':'); - if (colon >= 0) - host = host.Substring(0, colon); - - string base_uri = string.Format("{0}://{1}:{2}", RequestScheme, host, LocalEndPoint.Port); - - if (!Uri.TryCreate(base_uri + path, UriKind.Absolute, out _requestUri)) - { - _context.ErrorMessage = System.Net.WebUtility.HtmlEncode("Invalid url: " + base_uri + path); - return; - } - - _requestUri = HttpListenerRequestUriBuilder.GetRequestUri(_rawUrl, _requestUri.Scheme, - _requestUri.Authority, _requestUri.LocalPath, _requestUri.Query); - - if (_version >= HttpVersion.Version11) - { - string t_encoding = Headers[HttpKnownHeaderNames.TransferEncoding]; - _isChunked = (t_encoding != null && string.Equals(t_encoding, "chunked", StringComparison.OrdinalIgnoreCase)); - // 'identity' is not valid! - if (t_encoding != null && !_isChunked) - { - _context.Connection.SendError(null, 501); - return; - } - } - - if (!_isChunked && !_clSet) - { - if (string.Equals(_method, "POST", StringComparison.OrdinalIgnoreCase) || - string.Equals(_method, "PUT", StringComparison.OrdinalIgnoreCase)) - { - _context.Connection.SendError(null, 411); - return; - } - } - - if (string.Compare(Headers[HttpKnownHeaderNames.Expect], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) - { - HttpResponseStream output = _context.Connection.GetResponseStream(); - output.InternalWrite(s_100continue, 0, s_100continue.Length); - } - } - - internal static string Unquote(string str) - { - int start = str.IndexOf('\"'); - int end = str.LastIndexOf('\"'); - if (start >= 0 && end >= 0) - str = str.Substring(start + 1, end - 1); - return str.Trim(); - } - - internal void AddHeader(string header) - { - int colon = header.IndexOf(':'); - if (colon == -1 || colon == 0) - { - _context.ErrorMessage = HttpStatusDescription.Get(400); - _context.ErrorStatus = 400; - return; - } - - string name = header.Substring(0, colon).Trim(); - string val = header.Substring(colon + 1).Trim(); - if (name.Equals("content-length", StringComparison.OrdinalIgnoreCase)) - { - // To match Windows behavior: - // Content lengths >= 0 and <= long.MaxValue are accepted as is. - // Content lengths > long.MaxValue and <= ulong.MaxValue are treated as 0. - // Content lengths < 0 cause the requests to fail. - // Other input is a failure, too. - long parsedContentLength = - ulong.TryParse(val, out ulong parsedUlongContentLength) ? (parsedUlongContentLength <= long.MaxValue ? (long)parsedUlongContentLength : 0) : - long.Parse(val); - if (parsedContentLength < 0 || (_clSet && parsedContentLength != _contentLength)) - { - _context.ErrorMessage = "Invalid Content-Length."; - } - else - { - _contentLength = parsedContentLength; - _clSet = true; - } - } - else if (name.Equals("transfer-encoding", StringComparison.OrdinalIgnoreCase)) - { - if (Headers[HttpKnownHeaderNames.TransferEncoding] != null) - { - _context.ErrorStatus = (int)HttpStatusCode.NotImplemented; - _context.ErrorMessage = HttpStatusDescription.Get(HttpStatusCode.NotImplemented); - } - } - - if (_context.ErrorMessage == null) - { - _headers.Set(name, val); - } - } - - // returns true is the stream could be reused. - internal bool FlushInput() - { - if (!HasEntityBody) - return true; - - int length = 2048; - if (_contentLength > 0) - length = (int)Math.Min(_contentLength, (long)length); - - byte[] bytes = new byte[length]; - while (true) - { - try - { - IAsyncResult ares = InputStream.BeginRead(bytes, 0, length, null, null); - if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne(1000)) - return false; - if (InputStream.EndRead(ares) <= 0) - return true; - } - catch (ObjectDisposedException) - { - _inputStream = null; - return true; - } - catch - { - return false; - } - } - } - - public long ContentLength64 - { - get - { - if (_isChunked) - _contentLength = -1; - - return _contentLength; - } - } - - public bool HasEntityBody => (_contentLength > 0 || _isChunked); - - public QueryParamCollection Headers => _headers; - - public string HttpMethod => _method; - - public Stream InputStream - { - get - { - if (_inputStream == null) - { - if (_isChunked || _contentLength > 0) - _inputStream = _context.Connection.GetRequestStream(_isChunked, _contentLength); - else - _inputStream = Stream.Null; - } - - return _inputStream; - } - } - - public bool IsAuthenticated => false; - - public bool IsSecureConnection => _context.Connection.IsSecure; - - public System.Net.IPEndPoint LocalEndPoint => _context.Connection.LocalEndPoint; - - public System.Net.IPEndPoint RemoteEndPoint => _context.Connection.RemoteEndPoint; - - public Guid RequestTraceIdentifier { get; } = Guid.NewGuid(); - - public string ServiceName => null; - - private Uri RequestUri => _requestUri; - private bool SupportsWebSockets => true; - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequest.cs b/SocketHttpListener/Net/HttpListenerRequest.cs deleted file mode 100644 index 667d58ea7b..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequest.cs +++ /dev/null @@ -1,537 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net.WebSockets; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerRequest - { - private CookieCollection _cookies; - private bool? _keepAlive; - private string _rawUrl; - private Uri _requestUri; - private Version _version; - - public string[] AcceptTypes => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.Accept]); - - public string[] UserLanguages => Helpers.ParseMultivalueHeader(Headers[HttpKnownHeaderNames.AcceptLanguage]); - - private static CookieCollection ParseCookies(Uri uri, string setCookieHeader) - { - var cookies = new CookieCollection(); - return cookies; - } - - public CookieCollection Cookies - { - get - { - if (_cookies == null) - { - string cookieString = Headers[HttpKnownHeaderNames.Cookie]; - if (!string.IsNullOrEmpty(cookieString)) - { - _cookies = ParseCookies(RequestUri, cookieString); - } - if (_cookies == null) - { - _cookies = new CookieCollection(); - } - } - return _cookies; - } - } - - public Encoding ContentEncoding - { - get - { - if (UserAgent != null && CultureInfo.InvariantCulture.CompareInfo.IsPrefix(UserAgent, "UP")) - { - string postDataCharset = Headers["x-up-devcap-post-charset"]; - if (postDataCharset != null && postDataCharset.Length > 0) - { - try - { - return Encoding.GetEncoding(postDataCharset); - } - catch (ArgumentException) - { - } - } - } - if (HasEntityBody) - { - if (ContentType != null) - { - string charSet = Helpers.GetCharSetValueFromHeader(ContentType); - if (charSet != null) - { - try - { - return Encoding.GetEncoding(charSet); - } - catch (ArgumentException) - { - } - } - } - } - return Encoding.UTF8; - } - } - - public string ContentType => Headers[HttpKnownHeaderNames.ContentType]; - - public bool IsLocal => LocalEndPoint.Address.Equals(RemoteEndPoint.Address); - - public bool IsWebSocketRequest - { - get - { - if (!SupportsWebSockets) - { - return false; - } - - bool foundConnectionUpgradeHeader = false; - if (string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Connection]) || string.IsNullOrEmpty(Headers[HttpKnownHeaderNames.Upgrade])) - { - return false; - } - - foreach (string connection in Headers.GetValues(HttpKnownHeaderNames.Connection)) - { - if (string.Equals(connection, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase)) - { - foundConnectionUpgradeHeader = true; - break; - } - } - - if (!foundConnectionUpgradeHeader) - { - return false; - } - - foreach (string upgrade in Headers.GetValues(HttpKnownHeaderNames.Upgrade)) - { - if (string.Equals(upgrade, HttpWebSocket.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - return false; - } - } - - public bool KeepAlive - { - get - { - if (!_keepAlive.HasValue) - { - string header = Headers[HttpKnownHeaderNames.ProxyConnection]; - if (string.IsNullOrEmpty(header)) - { - header = Headers[HttpKnownHeaderNames.Connection]; - } - if (string.IsNullOrEmpty(header)) - { - if (ProtocolVersion >= HttpVersion.Version11) - { - _keepAlive = true; - } - else - { - header = Headers[HttpKnownHeaderNames.KeepAlive]; - _keepAlive = !string.IsNullOrEmpty(header); - } - } - else - { - header = header.ToLowerInvariant(); - _keepAlive = - header.IndexOf("close", StringComparison.OrdinalIgnoreCase) < 0 || - header.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) >= 0; - } - } - - return _keepAlive.Value; - } - } - - public QueryParamCollection QueryString - { - get - { - var queryString = new QueryParamCollection(); - Helpers.FillFromString(queryString, Url.Query, true, ContentEncoding); - return queryString; - } - } - - public string RawUrl => _rawUrl; - - private string RequestScheme => IsSecureConnection ? UriScheme.Https : UriScheme.Http; - - public string UserAgent => Headers[HttpKnownHeaderNames.UserAgent]; - - public string UserHostAddress => LocalEndPoint.ToString(); - - public string UserHostName => Headers[HttpKnownHeaderNames.Host]; - - public Uri UrlReferrer - { - get - { - string referrer = Headers[HttpKnownHeaderNames.Referer]; - if (referrer == null) - { - return null; - } - - bool success = Uri.TryCreate(referrer, UriKind.RelativeOrAbsolute, out var urlReferrer); - return success ? urlReferrer : null; - } - } - - public Uri Url => RequestUri; - - public Version ProtocolVersion => _version; - - private static class Helpers - { - // - // Get attribute off header value - // - internal static string GetCharSetValueFromHeader(string headerValue) - { - const string AttrName = "charset"; - - if (headerValue == null) - return null; - - int l = headerValue.Length; - int k = AttrName.Length; - - // find properly separated attribute name - int i = 1; // start searching from 1 - - while (i < l) - { - i = CultureInfo.InvariantCulture.CompareInfo.IndexOf(headerValue, AttrName, i, CompareOptions.IgnoreCase); - if (i < 0) - break; - if (i + k >= l) - break; - - char chPrev = headerValue[i - 1]; - char chNext = headerValue[i + k]; - if ((chPrev == ';' || chPrev == ',' || char.IsWhiteSpace(chPrev)) && (chNext == '=' || char.IsWhiteSpace(chNext))) - break; - - i += k; - } - - if (i < 0 || i >= l) - return null; - - // skip to '=' and the following whitespace - i += k; - while (i < l && char.IsWhiteSpace(headerValue[i])) - i++; - if (i >= l || headerValue[i] != '=') - return null; - i++; - while (i < l && char.IsWhiteSpace(headerValue[i])) - i++; - if (i >= l) - return null; - - // parse the value - string attrValue = null; - - int j; - - if (i < l && headerValue[i] == '"') - { - if (i == l - 1) - return null; - j = headerValue.IndexOf('"', i + 1); - if (j < 0 || j == i + 1) - return null; - - attrValue = headerValue.Substring(i + 1, j - i - 1).Trim(); - } - else - { - for (j = i; j < l; j++) - { - if (headerValue[j] == ';') - break; - } - - if (j == i) - return null; - - attrValue = headerValue.Substring(i, j - i).Trim(); - } - - return attrValue; - } - - internal static string[] ParseMultivalueHeader(string s) - { - if (s == null) - return null; - - int l = s.Length; - - // collect comma-separated values into list - - var values = new List(); - int i = 0; - - while (i < l) - { - // find next , - int ci = s.IndexOf(',', i); - if (ci < 0) - ci = l; - - // append corresponding server value - values.Add(s.Substring(i, ci - i)); - - // move to next - i = ci + 1; - - // skip leading space - if (i < l && s[i] == ' ') - i++; - } - - // return list as array of strings - - int n = values.Count; - string[] strings; - - // if n is 0 that means s was empty string - - if (n == 0) - { - strings = new string[1]; - strings[0] = string.Empty; - } - else - { - strings = new string[n]; - values.CopyTo(0, strings, 0, n); - } - return strings; - } - - - private static string UrlDecodeStringFromStringInternal(string s, Encoding e) - { - int count = s.Length; - var helper = new UrlDecoder(count, e); - - // go through the string's chars collapsing %XX and %uXXXX and - // appending each char as char, with exception of %XX constructs - // that are appended as bytes - - for (int pos = 0; pos < count; pos++) - { - char ch = s[pos]; - - if (ch == '+') - { - ch = ' '; - } - else if (ch == '%' && pos < count - 2) - { - if (s[pos + 1] == 'u' && pos < count - 5) - { - int h1 = HexToInt(s[pos + 2]); - int h2 = HexToInt(s[pos + 3]); - int h3 = HexToInt(s[pos + 4]); - int h4 = HexToInt(s[pos + 5]); - - if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0) - { // valid 4 hex chars - ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4); - pos += 5; - - // only add as char - helper.AddChar(ch); - continue; - } - } - else - { - int h1 = HexToInt(s[pos + 1]); - int h2 = HexToInt(s[pos + 2]); - - if (h1 >= 0 && h2 >= 0) - { // valid 2 hex chars - byte b = (byte)((h1 << 4) | h2); - pos += 2; - - // don't add as char - helper.AddByte(b); - continue; - } - } - } - - if ((ch & 0xFF80) == 0) - helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode - else - helper.AddChar(ch); - } - - return helper.GetString(); - } - - private static int HexToInt(char h) - { - return (h >= '0' && h <= '9') ? h - '0' : - (h >= 'a' && h <= 'f') ? h - 'a' + 10 : - (h >= 'A' && h <= 'F') ? h - 'A' + 10 : - -1; - } - - private class UrlDecoder - { - private int _bufferSize; - - // Accumulate characters in a special array - private int _numChars; - private char[] _charBuffer; - - // Accumulate bytes for decoding into characters in a special array - private int _numBytes; - private byte[] _byteBuffer; - - // Encoding to convert chars to bytes - private Encoding _encoding; - - private void FlushBytes() - { - if (_numBytes > 0) - { - _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars); - _numBytes = 0; - } - } - - internal UrlDecoder(int bufferSize, Encoding encoding) - { - _bufferSize = bufferSize; - _encoding = encoding; - - _charBuffer = new char[bufferSize]; - // byte buffer created on demand - } - - internal void AddChar(char ch) - { - if (_numBytes > 0) - FlushBytes(); - - _charBuffer[_numChars++] = ch; - } - - internal void AddByte(byte b) - { - { - if (_byteBuffer == null) - _byteBuffer = new byte[_bufferSize]; - - _byteBuffer[_numBytes++] = b; - } - } - - internal string GetString() - { - if (_numBytes > 0) - FlushBytes(); - - if (_numChars > 0) - return new string(_charBuffer, 0, _numChars); - else - return string.Empty; - } - } - - - internal static void FillFromString(QueryParamCollection nvc, string s, bool urlencoded, Encoding encoding) - { - int l = (s != null) ? s.Length : 0; - int i = (s.Length > 0 && s[0] == '?') ? 1 : 0; - - while (i < l) - { - // find next & while noting first = on the way (and if there are more) - - int si = i; - int ti = -1; - - while (i < l) - { - char ch = s[i]; - - if (ch == '=') - { - if (ti < 0) - ti = i; - } - else if (ch == '&') - { - break; - } - - i++; - } - - // extract the name / value pair - - string name = null; - string value = null; - - if (ti >= 0) - { - name = s.Substring(si, ti - si); - value = s.Substring(ti + 1, i - ti - 1); - } - else - { - value = s.Substring(si, i - si); - } - - // add name / value pair to the collection - - if (urlencoded) - nvc.Add( - name == null ? null : UrlDecodeStringFromStringInternal(name, encoding), - UrlDecodeStringFromStringInternal(value, encoding)); - else - nvc.Add(name, value); - - // trailing '&' - - if (i == l - 1 && s[i] == '&') - nvc.Add(null, ""); - - i++; - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs b/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs deleted file mode 100644 index 7b4b619e68..0000000000 --- a/SocketHttpListener/Net/HttpListenerRequestUriBuilder.cs +++ /dev/null @@ -1,443 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Text; - -namespace SocketHttpListener.Net -{ - // We don't use the cooked URL because http.sys unescapes all percent-encoded values. However, - // we also can't just use the raw Uri, since http.sys supports not only Utf-8, but also ANSI/DBCS and - // Unicode code points. System.Uri only supports Utf-8. - // The purpose of this class is to convert all ANSI, DBCS, and Unicode code points into percent encoded - // Utf-8 characters. - internal sealed class HttpListenerRequestUriBuilder - { - private static readonly Encoding s_utf8Encoding = new UTF8Encoding(false, true); - private static readonly Encoding s_ansiEncoding = Encoding.GetEncoding(0, new EncoderExceptionFallback(), new DecoderExceptionFallback()); - - private readonly string _rawUri; - private readonly string _cookedUriScheme; - private readonly string _cookedUriHost; - private readonly string _cookedUriPath; - private readonly string _cookedUriQuery; - - // This field is used to build the final request Uri string from the Uri parts passed to the ctor. - private StringBuilder _requestUriString; - - // The raw path is parsed by looping through all characters from left to right. 'rawOctets' - // is used to store consecutive percent encoded octets as actual byte values: e.g. for path /pa%C3%84th%2F/ - // rawOctets will be set to { 0xC3, 0x84 } when we reach character 't' and it will be { 0x2F } when - // we reach the final '/'. I.e. after a sequence of percent encoded octets ends, we use rawOctets as - // input to the encoding and percent encode the resulting string into UTF-8 octets. - // - // When parsing ANSI (Latin 1) encoded path '/pa%C4th/', %C4 will be added to rawOctets and when - // we reach 't', the content of rawOctets { 0xC4 } will be fed into the ANSI encoding. The resulting - // string 'Ä' will be percent encoded into UTF-8 octets and appended to requestUriString. The final - // path will be '/pa%C3%84th/', where '%C3%84' is the UTF-8 percent encoded character 'Ä'. - private List _rawOctets; - private string _rawPath; - - // Holds the final request Uri. - private Uri _requestUri; - - private HttpListenerRequestUriBuilder(string rawUri, string cookedUriScheme, string cookedUriHost, - string cookedUriPath, string cookedUriQuery) - { - _rawUri = rawUri; - _cookedUriScheme = cookedUriScheme; - _cookedUriHost = cookedUriHost; - _cookedUriPath = AddSlashToAsteriskOnlyPath(cookedUriPath); - _cookedUriQuery = cookedUriQuery ?? string.Empty; - } - - public static Uri GetRequestUri(string rawUri, string cookedUriScheme, string cookedUriHost, - string cookedUriPath, string cookedUriQuery) - { - var builder = new HttpListenerRequestUriBuilder(rawUri, - cookedUriScheme, cookedUriHost, cookedUriPath, cookedUriQuery); - - return builder.Build(); - } - - private Uri Build() - { - BuildRequestUriUsingRawPath(); - - if (_requestUri == null) - { - BuildRequestUriUsingCookedPath(); - } - - return _requestUri; - } - - private void BuildRequestUriUsingCookedPath() - { - bool isValid = Uri.TryCreate(_cookedUriScheme + Uri.SchemeDelimiter + _cookedUriHost + _cookedUriPath + - _cookedUriQuery, UriKind.Absolute, out _requestUri); - - // Creating a Uri from the cooked Uri should really always work: If not, we log at least. - if (!isValid) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _cookedUriPath, _cookedUriQuery)); - } - } - - private void BuildRequestUriUsingRawPath() - { - bool isValid = false; - - // Initialize 'rawPath' only if really needed; i.e. if we build the request Uri from the raw Uri. - _rawPath = GetPath(_rawUri); - - // Try to check the raw path using first the primary encoding (according to http.sys settings); - // if it fails try the secondary encoding. - ParsingResult result = BuildRequestUriUsingRawPath(GetEncoding(EncodingType.Primary)); - if (result == ParsingResult.EncodingError) - { - Encoding secondaryEncoding = GetEncoding(EncodingType.Secondary); - result = BuildRequestUriUsingRawPath(secondaryEncoding); - } - isValid = (result == ParsingResult.Success) ? true : false; - - // Log that we weren't able to create a Uri from the raw string. - if (!isValid) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_create_uri, _cookedUriScheme, _cookedUriHost, _rawPath, _cookedUriQuery)); - } - } - - private static Encoding GetEncoding(EncodingType type) - { - Debug.Assert((type == EncodingType.Primary) || (type == EncodingType.Secondary), - "Unknown 'EncodingType' value: " + type.ToString()); - - if (type == EncodingType.Secondary) - { - return s_ansiEncoding; - } - else - { - return s_utf8Encoding; - } - } - - private ParsingResult BuildRequestUriUsingRawPath(Encoding encoding) - { - Debug.Assert(encoding != null, "'encoding' must be assigned."); - Debug.Assert(!string.IsNullOrEmpty(_rawPath), "'rawPath' must have at least one character."); - - _rawOctets = new List(); - _requestUriString = new StringBuilder(); - _requestUriString.Append(_cookedUriScheme); - _requestUriString.Append(Uri.SchemeDelimiter); - _requestUriString.Append(_cookedUriHost); - - ParsingResult result = ParseRawPath(encoding); - if (result == ParsingResult.Success) - { - _requestUriString.Append(_cookedUriQuery); - - Debug.Assert(_rawOctets.Count == 0, - "Still raw octets left. They must be added to the result path."); - - if (!Uri.TryCreate(_requestUriString.ToString(), UriKind.Absolute, out _requestUri)) - { - // If we can't create a Uri from the string, this is an invalid string and it doesn't make - // sense to try another encoding. - result = ParsingResult.InvalidString; - } - } - - if (result != ParsingResult.Success) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_raw_path, _rawPath, encoding.EncodingName)); - } - - return result; - } - - private ParsingResult ParseRawPath(Encoding encoding) - { - Debug.Assert(encoding != null, "'encoding' must be assigned."); - - int index = 0; - char current = '\0'; - while (index < _rawPath.Length) - { - current = _rawPath[index]; - if (current == '%') - { - // Assert is enough, since http.sys accepted the request string already. This should never happen. - Debug.Assert(index + 2 < _rawPath.Length, "Expected >=2 characters after '%' (e.g. %2F)"); - - index++; - current = _rawPath[index]; - if (current == 'u' || current == 'U') - { - // We found "%u" which means, we have a Unicode code point of the form "%uXXXX". - Debug.Assert(index + 4 < _rawPath.Length, "Expected >=4 characters after '%u' (e.g. %u0062)"); - - // Decode the content of rawOctets into percent encoded UTF-8 characters and append them - // to requestUriString. - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - if (!AppendUnicodeCodePointValuePercentEncoded(_rawPath.Substring(index + 1, 4))) - { - return ParsingResult.InvalidString; - } - index += 5; - } - else - { - // We found '%', but not followed by 'u', i.e. we have a percent encoded octed: %XX - if (!AddPercentEncodedOctetToRawOctetsList(encoding, _rawPath.Substring(index, 2))) - { - return ParsingResult.InvalidString; - } - index += 2; - } - } - else - { - // We found a non-'%' character: decode the content of rawOctets into percent encoded - // UTF-8 characters and append it to the result. - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - // Append the current character to the result. - _requestUriString.Append(current); - index++; - } - } - - // if the raw path ends with a sequence of percent encoded octets, make sure those get added to the - // result (requestUriString). - if (!EmptyDecodeAndAppendRawOctetsList(encoding)) - { - return ParsingResult.EncodingError; - } - - return ParsingResult.Success; - } - - private bool AppendUnicodeCodePointValuePercentEncoded(string codePoint) - { - // http.sys only supports %uXXXX (4 hex-digits), even though unicode code points could have up to - // 6 hex digits. Therefore we parse always 4 characters after %u and convert them to an int. - if (!int.TryParse(codePoint, NumberStyles.HexNumber, null, out var codePointValue)) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint)); - return false; - } - - string unicodeString = null; - try - { - unicodeString = char.ConvertFromUtf32(codePointValue); - AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(unicodeString)); - - return true; - } - catch (ArgumentOutOfRangeException) - { - //if (NetEventSource.IsEnabled) - // NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, codePoint)); - } - catch (EncoderFallbackException) - { - // If utf8Encoding.GetBytes() fails - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, unicodeString, e.Message)); - } - - return false; - } - - private bool AddPercentEncodedOctetToRawOctetsList(Encoding encoding, string escapedCharacter) - { - if (!byte.TryParse(escapedCharacter, NumberStyles.HexNumber, null, out byte encodedValue)) - { - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_percent_value, escapedCharacter)); - return false; - } - - _rawOctets.Add(encodedValue); - - return true; - } - - private bool EmptyDecodeAndAppendRawOctetsList(Encoding encoding) - { - if (_rawOctets.Count == 0) - { - return true; - } - - string decodedString = null; - try - { - // If the encoding can get a string out of the byte array, this is a valid string in the - // 'encoding' encoding. - decodedString = encoding.GetString(_rawOctets.ToArray()); - - if (encoding == s_utf8Encoding) - { - AppendOctetsPercentEncoded(_requestUriString, _rawOctets.ToArray()); - } - else - { - AppendOctetsPercentEncoded(_requestUriString, s_utf8Encoding.GetBytes(decodedString)); - } - - _rawOctets.Clear(); - - return true; - } - catch (DecoderFallbackException) - { - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_bytes, GetOctetsAsString(_rawOctets), e.Message)); - } - catch (EncoderFallbackException) - { - // If utf8Encoding.GetBytes() fails - //if (NetEventSource.IsEnabled) NetEventSource.Error(this, SR.Format(SR.net_log_listener_cant_convert_to_utf8, decodedString, e.Message)); - } - - return false; - } - - private static void AppendOctetsPercentEncoded(StringBuilder target, IEnumerable octets) - { - foreach (byte octet in octets) - { - target.Append('%'); - target.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); - } - } - - private static string GetOctetsAsString(IEnumerable octets) - { - var octetString = new StringBuilder(); - - bool first = true; - foreach (byte octet in octets) - { - if (first) - { - first = false; - } - else - { - octetString.Append(' '); - } - octetString.Append(octet.ToString("X2", CultureInfo.InvariantCulture)); - } - - return octetString.ToString(); - } - - private static string GetPath(string uriString) - { - Debug.Assert(uriString != null, "uriString must not be null"); - Debug.Assert(uriString.Length > 0, "uriString must not be empty"); - - int pathStartIndex = 0; - - // Perf. improvement: nearly all strings are relative Uris. So just look if the - // string starts with '/'. If so, we have a relative Uri and the path starts at position 0. - // (http.sys already trimmed leading whitespaces) - if (uriString[0] != '/') - { - // We can't check against cookedUriScheme, since http.sys allows for request http://myserver/ to - // use a request line 'GET https://myserver/' (note http vs. https). Therefore check if the - // Uri starts with either http:// or https://. - int authorityStartIndex = 0; - if (uriString.StartsWith("http://", StringComparison.OrdinalIgnoreCase)) - { - authorityStartIndex = 7; - } - else if (uriString.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) - { - authorityStartIndex = 8; - } - - if (authorityStartIndex > 0) - { - // we have an absolute Uri. Find out where the authority ends and the path begins. - // Note that Uris like "http://server?query=value/1/2" are invalid according to RFC2616 - // and http.sys behavior: If the Uri contains a query, there must be at least one '/' - // between the authority and the '?' character: It's safe to just look for the first - // '/' after the authority to determine the beginning of the path. - pathStartIndex = uriString.IndexOf('/', authorityStartIndex); - if (pathStartIndex == -1) - { - // e.g. for request lines like: 'GET http://myserver' (no final '/') - pathStartIndex = uriString.Length; - } - } - else - { - // RFC2616: Request-URI = "*" | absoluteURI | abs_path | authority - // 'authority' can only be used with CONNECT which is never received by HttpListener. - // I.e. if we don't have an absolute path (must start with '/') and we don't have - // an absolute Uri (must start with http:// or https://), then 'uriString' must be '*'. - Debug.Assert((uriString.Length == 1) && (uriString[0] == '*'), "Unknown request Uri string format", - "Request Uri string is not an absolute Uri, absolute path, or '*': {0}", uriString); - - // Should we ever get here, be consistent with 2.0/3.5 behavior: just add an initial - // slash to the string and treat it as a path: - uriString = "/" + uriString; - } - } - - // Find end of path: The path is terminated by - // - the first '?' character - // - the first '#' character: This is never the case here, since http.sys won't accept - // Uris containing fragments. Also, RFC2616 doesn't allow fragments in request Uris. - // - end of Uri string - int queryIndex = uriString.IndexOf('?'); - if (queryIndex == -1) - { - queryIndex = uriString.Length; - } - - // will always return a != null string. - return AddSlashToAsteriskOnlyPath(uriString.Substring(pathStartIndex, queryIndex - pathStartIndex)); - } - - private static string AddSlashToAsteriskOnlyPath(string path) - { - Debug.Assert(path != null, "'path' must not be null"); - - // If a request like "OPTIONS * HTTP/1.1" is sent to the listener, then the request Uri - // should be "http[s]://server[:port]/*" to be compatible with pre-4.0 behavior. - if ((path.Length == 1) && (path[0] == '*')) - { - return "/*"; - } - - return path; - } - - private enum ParsingResult - { - Success, - InvalidString, - EncodingError - } - - private enum EncodingType - { - Primary, - Secondary - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerResponse.Managed.cs b/SocketHttpListener/Net/HttpListenerResponse.Managed.cs deleted file mode 100644 index f595fce7cc..0000000000 --- a/SocketHttpListener/Net/HttpListenerResponse.Managed.cs +++ /dev/null @@ -1,333 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerResponse : IDisposable - { - private long _contentLength; - private Version _version = HttpVersion.Version11; - private int _statusCode = 200; - internal object _headersLock = new object(); - private bool _forceCloseChunked; - - internal HttpListenerResponse(HttpListenerContext context) - { - _httpContext = context; - } - - internal bool ForceCloseChunked => _forceCloseChunked; - - private void EnsureResponseStream() - { - if (_responseStream == null) - { - _responseStream = _httpContext.Connection.GetResponseStream(); - } - } - - public Version ProtocolVersion - { - get => _version; - set - { - CheckDisposed(); - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1)) - { - throw new ArgumentException("Wrong version"); - } - - _version = new Version(value.Major, value.Minor); // match Windows behavior, trimming to just Major.Minor - } - } - - public int StatusCode - { - get => _statusCode; - set - { - CheckDisposed(); - - if (value < 100 || value > 999) - throw new ProtocolViolationException("Invalid status"); - - _statusCode = value; - } - } - - private void Dispose() - { - Close(true); - } - - public void Close() - { - if (Disposed) - return; - - Close(false); - } - - public void Abort() - { - if (Disposed) - return; - - Close(true); - } - - private void Close(bool force) - { - Disposed = true; - _httpContext.Connection.Close(force); - } - - public void Close(byte[] responseEntity, bool willBlock) - { - CheckDisposed(); - - if (responseEntity == null) - { - throw new ArgumentNullException(nameof(responseEntity)); - } - - if (!SentHeaders && _boundaryType != BoundaryType.Chunked) - { - ContentLength64 = responseEntity.Length; - } - - if (willBlock) - { - try - { - OutputStream.Write(responseEntity, 0, responseEntity.Length); - } - finally - { - Close(false); - } - } - else - { - OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar => - { - var thisRef = (HttpListenerResponse)iar.AsyncState; - try - { - try - { - thisRef.OutputStream.EndWrite(iar); - } - finally - { - thisRef.Close(false); - } - } - catch (Exception) - { - // In case response was disposed during this time - } - }, this); - } - } - - public void CopyFrom(HttpListenerResponse templateResponse) - { - _webHeaders.Clear(); - //_webHeaders.Add(templateResponse._webHeaders); - _contentLength = templateResponse._contentLength; - _statusCode = templateResponse._statusCode; - _statusDescription = templateResponse._statusDescription; - _keepAlive = templateResponse._keepAlive; - _version = templateResponse._version; - } - - internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandshake = false) - { - if (!isWebSocketHandshake) - { - if (_webHeaders["Server"] == null) - { - _webHeaders.Set("Server", "Microsoft-NetCore/2.0"); - } - - if (_webHeaders["Date"] == null) - { - _webHeaders.Set("Date", DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture)); - } - - if (_boundaryType == BoundaryType.None) - { - if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10) - { - _keepAlive = false; - } - else - { - _boundaryType = BoundaryType.Chunked; - } - - if (CanSendResponseBody(_httpContext.Response.StatusCode)) - { - _contentLength = -1; - } - else - { - _boundaryType = BoundaryType.ContentLength; - _contentLength = 0; - } - } - - if (_boundaryType != BoundaryType.Chunked) - { - if (_boundaryType != BoundaryType.ContentLength && closing) - { - _contentLength = CanSendResponseBody(_httpContext.Response.StatusCode) ? -1 : 0; - } - - if (_boundaryType == BoundaryType.ContentLength) - { - _webHeaders.Set("Content-Length", _contentLength.ToString("D", CultureInfo.InvariantCulture)); - } - } - - /* Apache forces closing the connection for these status codes: - * HttpStatusCode.BadRequest 400 - * HttpStatusCode.RequestTimeout 408 - * HttpStatusCode.LengthRequired 411 - * HttpStatusCode.RequestEntityTooLarge 413 - * HttpStatusCode.RequestUriTooLong 414 - * HttpStatusCode.InternalServerError 500 - * HttpStatusCode.ServiceUnavailable 503 - */ - bool conn_close = (_statusCode == (int)HttpStatusCode.BadRequest || _statusCode == (int)HttpStatusCode.RequestTimeout - || _statusCode == (int)HttpStatusCode.LengthRequired || _statusCode == (int)HttpStatusCode.RequestEntityTooLarge - || _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError - || _statusCode == (int)HttpStatusCode.ServiceUnavailable); - - if (!conn_close) - { - conn_close = !_httpContext.Request.KeepAlive; - } - - // They sent both KeepAlive: true and Connection: close - if (!_keepAlive || conn_close) - { - _webHeaders.Set("Connection", "Close"); - conn_close = true; - } - - if (SendChunked) - { - _webHeaders.Set("Transfer-Encoding", "Chunked"); - } - - int reuses = _httpContext.Connection.Reuses; - if (reuses >= 100) - { - _forceCloseChunked = true; - if (!conn_close) - { - _webHeaders.Set("Connection", "Close"); - conn_close = true; - } - } - - if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10) - { - if (_keepAlive) - { - Headers["Keep-Alive"] = "true"; - } - - if (!conn_close) - { - _webHeaders.Set("Connection", "Keep-Alive"); - } - } - - ComputeCookies(); - } - - var encoding = Encoding.UTF8; - var writer = new StreamWriter(ms, encoding, 256); - writer.Write("HTTP/1.1 {0} ", _statusCode); // "1.1" matches Windows implementation, which ignores the response version - writer.Flush(); - byte[] statusDescriptionBytes = WebHeaderEncoding.GetBytes(StatusDescription); - ms.Write(statusDescriptionBytes, 0, statusDescriptionBytes.Length); - writer.Write("\r\n"); - - writer.Write(FormatHeaders(_webHeaders)); - writer.Flush(); - int preamble = encoding.GetPreamble().Length; - EnsureResponseStream(); - - /* Assumes that the ms was at position 0 */ - ms.Position = preamble; - SentHeaders = !isWebSocketHandshake; - } - - private static bool HeaderCanHaveEmptyValue(string name) => - !string.Equals(name, "Location", StringComparison.OrdinalIgnoreCase); - - private static string FormatHeaders(WebHeaderCollection headers) - { - var sb = new StringBuilder(); - - for (int i = 0; i < headers.Count; i++) - { - string key = headers.GetKey(i); - string[] values = headers.GetValues(i); - - int startingLength = sb.Length; - - sb.Append(key).Append(": "); - bool anyValues = false; - for (int j = 0; j < values.Length; j++) - { - string value = values[j]; - if (!string.IsNullOrWhiteSpace(value)) - { - if (anyValues) - { - sb.Append(", "); - } - sb.Append(value); - anyValues = true; - } - } - - if (anyValues || HeaderCanHaveEmptyValue(key)) - { - // Complete the header - sb.Append("\r\n"); - } - else - { - // Empty header; remove it. - sb.Length = startingLength; - } - } - - return sb.Append("\r\n").ToString(); - } - - private bool Disposed { get; set; } - internal bool SentHeaders { get; set; } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - return ((HttpResponseStream)OutputStream).TransmitFile(path, offset, count, fileShareMode, cancellationToken); - } - } -} diff --git a/SocketHttpListener/Net/HttpListenerResponse.cs b/SocketHttpListener/Net/HttpListenerResponse.cs deleted file mode 100644 index 66ef885643..0000000000 --- a/SocketHttpListener/Net/HttpListenerResponse.cs +++ /dev/null @@ -1,294 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - public sealed partial class HttpListenerResponse : IDisposable - { - private BoundaryType _boundaryType = BoundaryType.None; - private CookieCollection _cookies; - private HttpListenerContext _httpContext; - private bool _keepAlive = true; - private HttpResponseStream _responseStream; - private string _statusDescription; - private WebHeaderCollection _webHeaders = new WebHeaderCollection(); - - public WebHeaderCollection Headers => _webHeaders; - - public Encoding ContentEncoding { get; set; } - - public string ContentType - { - get => Headers["Content-Type"]; - set - { - CheckDisposed(); - if (string.IsNullOrEmpty(value)) - { - Headers.Remove("Content-Type"); - } - else - { - Headers.Set("Content-Type", value); - } - } - } - - private HttpListenerContext HttpListenerContext => _httpContext; - - private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request; - - internal EntitySendFormat EntitySendFormat - { - get => (EntitySendFormat)_boundaryType; - set - { - CheckDisposed(); - CheckSentHeaders(); - if (value == EntitySendFormat.Chunked && HttpListenerRequest.ProtocolVersion.Minor == 0) - { - throw new ProtocolViolationException("net_nochunkuploadonhttp10"); - } - _boundaryType = (BoundaryType)value; - if (value != EntitySendFormat.ContentLength) - { - _contentLength = -1; - } - } - } - - public bool SendChunked - { - get => EntitySendFormat == EntitySendFormat.Chunked; - set => EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength; - } - - // We MUST NOT send message-body when we send responses with these Status codes - private static readonly int[] s_noResponseBody = { 100, 101, 204, 205, 304 }; - - private static bool CanSendResponseBody(int responseCode) - { - for (int i = 0; i < s_noResponseBody.Length; i++) - { - if (responseCode == s_noResponseBody[i]) - { - return false; - } - } - return true; - } - - public long ContentLength64 - { - get => _contentLength; - set - { - CheckDisposed(); - CheckSentHeaders(); - if (value >= 0) - { - _contentLength = value; - _boundaryType = BoundaryType.ContentLength; - } - else - { - throw new ArgumentOutOfRangeException(nameof(value)); - } - } - } - - public CookieCollection Cookies - { - get => _cookies ?? (_cookies = new CookieCollection()); - set => _cookies = value; - } - - public bool KeepAlive - { - get => _keepAlive; - set - { - CheckDisposed(); - _keepAlive = value; - } - } - - public Stream OutputStream - { - get - { - CheckDisposed(); - EnsureResponseStream(); - return _responseStream; - } - } - - public string RedirectLocation - { - get => Headers["Location"]; - set - { - // note that this doesn't set the status code to a redirect one - CheckDisposed(); - if (string.IsNullOrEmpty(value)) - { - Headers.Remove("Location"); - } - else - { - Headers.Set("Location", value); - } - } - } - - public string StatusDescription - { - get - { - if (_statusDescription == null) - { - // if the user hasn't set this, generated on the fly, if possible. - // We know this one is safe, no need to verify it as in the setter. - _statusDescription = HttpStatusDescription.Get(StatusCode); - } - if (_statusDescription == null) - { - _statusDescription = string.Empty; - } - return _statusDescription; - } - set - { - CheckDisposed(); - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - // Need to verify the status description doesn't contain any control characters except HT. We mask off the high - // byte since that's how it's encoded. - for (int i = 0; i < value.Length; i++) - { - char c = (char)(0x000000ff & (uint)value[i]); - if ((c <= 31 && c != (byte)'\t') || c == 127) - { - throw new ArgumentException("net_WebHeaderInvalidControlChars"); - } - } - - _statusDescription = value; - } - } - - public void AddHeader(string name, string value) - { - Headers.Set(name, value); - } - - public void AppendHeader(string name, string value) - { - Headers.Add(name, value); - } - - public void AppendCookie(Cookie cookie) - { - if (cookie == null) - { - throw new ArgumentNullException(nameof(cookie)); - } - Cookies.Add(cookie); - } - - private void ComputeCookies() - { - if (_cookies != null) - { - // now go through the collection, and concatenate all the cookies in per-variant strings - //string setCookie2 = null, setCookie = null; - //for (int index = 0; index < _cookies.Count; index++) - //{ - // Cookie cookie = _cookies[index]; - // string cookieString = cookie.ToServerString(); - // if (cookieString == null || cookieString.Length == 0) - // { - // continue; - // } - - // if (cookie.IsRfc2965Variant()) - // { - // setCookie2 = setCookie2 == null ? cookieString : setCookie2 + ", " + cookieString; - // } - // else - // { - // setCookie = setCookie == null ? cookieString : setCookie + ", " + cookieString; - // } - //} - - //if (!string.IsNullOrEmpty(setCookie)) - //{ - // Headers.Set(HttpKnownHeaderNames.SetCookie, setCookie); - // if (string.IsNullOrEmpty(setCookie2)) - // { - // Headers.Remove(HttpKnownHeaderNames.SetCookie2); - // } - //} - - //if (!string.IsNullOrEmpty(setCookie2)) - //{ - // Headers.Set(HttpKnownHeaderNames.SetCookie2, setCookie2); - // if (string.IsNullOrEmpty(setCookie)) - // { - // Headers.Remove(HttpKnownHeaderNames.SetCookie); - // } - //} - } - } - - public void Redirect(string url) - { - Headers["Location"] = url; - StatusCode = (int)HttpStatusCode.Redirect; - StatusDescription = "Found"; - } - - public void SetCookie(Cookie cookie) - { - if (cookie == null) - { - throw new ArgumentNullException(nameof(cookie)); - } - - //Cookie newCookie = cookie.Clone(); - //int added = Cookies.InternalAdd(newCookie, true); - - //if (added != 1) - //{ - // // The Cookie already existed and couldn't be replaced. - // throw new ArgumentException("Cookie exists"); - //} - } - - void IDisposable.Dispose() - { - Dispose(); - } - - private void CheckDisposed() - { - if (Disposed) - { - throw new ObjectDisposedException(GetType().FullName); - } - } - - private void CheckSentHeaders() - { - if (SentHeaders) - { - throw new InvalidOperationException(); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpRequestStream.Managed.cs b/SocketHttpListener/Net/HttpRequestStream.Managed.cs deleted file mode 100644 index 42fc4d97c2..0000000000 --- a/SocketHttpListener/Net/HttpRequestStream.Managed.cs +++ /dev/null @@ -1,210 +0,0 @@ -using System; -using System.IO; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpRequestStream : Stream - { - private byte[] _buffer; - private int _offset; - private int _length; - private long _remainingBody; - protected bool _closed; - private Stream _stream; - - internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length) - : this(stream, buffer, offset, length, -1) - { - } - - internal HttpRequestStream(Stream stream, byte[] buffer, int offset, int length, long contentlength) - { - _stream = stream; - _buffer = buffer; - _offset = offset; - _length = length; - _remainingBody = contentlength; - } - - // Returns 0 if we can keep reading from the base stream, - // > 0 if we read something from the buffer. - // -1 if we had a content length set and we finished reading that many bytes. - private int FillFromBuffer(byte[] buffer, int offset, int count) - { - if (_remainingBody == 0) - return -1; - - if (_length == 0) - return 0; - - int size = Math.Min(_length, count); - if (_remainingBody > 0) - size = (int)Math.Min(size, _remainingBody); - - if (_offset > _buffer.Length - size) - { - size = Math.Min(size, _buffer.Length - _offset); - } - if (size == 0) - return 0; - - Buffer.BlockCopy(_buffer, _offset, buffer, offset, size); - _offset += size; - _length -= size; - if (_remainingBody > 0) - _remainingBody -= size; - return size; - } - - protected virtual int ReadCore(byte[] buffer, int offset, int size) - { - // Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0 - int nread = FillFromBuffer(buffer, offset, size); - if (nread == -1) - { // No more bytes available (Content-Length) - return 0; - } - else if (nread > 0) - { - return nread; - } - - if (_remainingBody > 0) - { - size = (int)Math.Min(_remainingBody, (long)size); - } - - nread = _stream.Read(buffer, offset, size); - - if (_remainingBody > 0) - { - if (nread == 0) - { - throw new Exception("Bad request"); - } - - //Debug.Assert(nread <= _remainingBody); - _remainingBody -= nread; - } - - return nread; - } - - protected virtual IAsyncResult BeginReadCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - if (size == 0 || _closed) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - ares.Complete(); - return ares; - } - - int nread = FillFromBuffer(buffer, offset, size); - if (nread > 0 || nread == -1) - { - var ares = new HttpStreamAsyncResult(this); - ares._buffer = buffer; - ares._offset = offset; - ares._count = size; - ares._callback = cback; - ares._state = state; - ares._synchRead = Math.Max(0, nread); - ares.Complete(); - return ares; - } - - // Avoid reading past the end of the request to allow - // for HTTP pipelining - if (_remainingBody >= 0 && size > _remainingBody) - { - size = (int)Math.Min(_remainingBody, (long)size); - } - - return _stream.BeginRead(buffer, offset, size, cback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - var r = asyncResult as HttpStreamAsyncResult; - if (r != null) - { - if (!ReferenceEquals(this, r._parent)) - { - throw new ArgumentException("Invalid async result"); - } - if (r._endCalled) - { - throw new InvalidOperationException("invalid end call"); - } - r._endCalled = true; - - if (!asyncResult.IsCompleted) - { - asyncResult.AsyncWaitHandle.WaitOne(); - } - - return r._synchRead; - } - - if (_closed) - return 0; - - int nread = 0; - try - { - nread = _stream.EndRead(asyncResult); - } - catch (IOException e) when (e.InnerException is ArgumentException || e.InnerException is InvalidOperationException) - { - throw e.InnerException; - } - - if (_remainingBody > 0) - { - if (nread == 0) - { - throw new Exception("Bad request"); - } - - _remainingBody -= nread; - } - - return nread; - } - } -} diff --git a/SocketHttpListener/Net/HttpRequestStream.cs b/SocketHttpListener/Net/HttpRequestStream.cs deleted file mode 100644 index 1c554df20b..0000000000 --- a/SocketHttpListener/Net/HttpRequestStream.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpRequestStream : Stream - { - public override bool CanSeek => false; - public override bool CanWrite => false; - public override bool CanRead => true; - - public override int Read(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - if (size == 0 || _closed) - { - return 0; - } - - return ReadCore(buffer, offset, size); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return BeginReadCore(buffer, offset, size, callback, state); - } - - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return base.BeginWrite(buffer, offset, count, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - base.EndWrite(asyncResult); - } - - internal bool Closed => _closed; - - protected override void Dispose(bool disposing) - { - _closed = true; - base.Dispose(disposing); - } - } -} diff --git a/SocketHttpListener/Net/HttpResponseStream.Managed.cs b/SocketHttpListener/Net/HttpResponseStream.Managed.cs deleted file mode 100644 index 5d02a9c956..0000000000 --- a/SocketHttpListener/Net/HttpResponseStream.Managed.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; - -namespace SocketHttpListener.Net -{ - // Licensed to the .NET Foundation under one or more agreements. - // See the LICENSE file in the project root for more information. - // - // System.Net.ResponseStream - // - // Author: - // Gonzalo Paniagua Javier (gonzalo@novell.com) - // - // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - // - // Permission is hereby granted, free of charge, to any person obtaining - // a copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to - // permit persons to whom the Software is furnished to do so, subject to - // the following conditions: - // - // The above copyright notice and this permission notice shall be - // included in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // - - internal partial class HttpResponseStream : Stream - { - private HttpListenerResponse _response; - private bool _ignore_errors; - private bool _trailer_sent; - private Stream _stream; - private readonly IStreamHelper _streamHelper; - private readonly Socket _socket; - private readonly bool _supportsDirectSocketAccess; - private readonly IEnvironmentInfo _environment; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - - internal HttpResponseStream(Stream stream, HttpListenerResponse response, bool ignore_errors, IStreamHelper streamHelper, Socket socket, bool supportsDirectSocketAccess, IEnvironmentInfo environment, IFileSystem fileSystem, ILogger logger) - { - _response = response; - _ignore_errors = ignore_errors; - _streamHelper = streamHelper; - _socket = socket; - _supportsDirectSocketAccess = supportsDirectSocketAccess; - _environment = environment; - _fileSystem = fileSystem; - _logger = logger; - _stream = stream; - } - - private void DisposeCore() - { - byte[] bytes = null; - MemoryStream ms = GetHeaders(true); - bool chunked = _response.SendChunked; - if (_stream.CanWrite) - { - try - { - if (ms != null) - { - long start = ms.Position; - if (chunked && !_trailer_sent) - { - bytes = GetChunkSizeBytes(0, true); - ms.Position = ms.Length; - ms.Write(bytes, 0, bytes.Length); - } - InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start)); - _trailer_sent = true; - } - else if (chunked && !_trailer_sent) - { - bytes = GetChunkSizeBytes(0, true); - InternalWrite(bytes, 0, bytes.Length); - _trailer_sent = true; - } - } - catch (HttpListenerException) - { - // Ignore error due to connection reset by peer - } - } - _response.Close(); - } - - internal async Task WriteWebSocketHandshakeHeadersAsync() - { - if (_closed) - throw new ObjectDisposedException(GetType().ToString()); - - if (_stream.CanWrite) - { - MemoryStream ms = GetHeaders(closing: false, isWebSocketHandshake: true); - bool chunked = _response.SendChunked; - - long start = ms.Position; - if (chunked) - { - byte[] bytes = GetChunkSizeBytes(0, true); - ms.Position = ms.Length; - ms.Write(bytes, 0, bytes.Length); - } - - await InternalWriteAsync(ms.GetBuffer(), (int)start, (int)(ms.Length - start)).ConfigureAwait(false); - await _stream.FlushAsync().ConfigureAwait(false); - } - } - - private MemoryStream GetHeaders(bool closing, bool isWebSocketHandshake = false) - { - //// SendHeaders works on shared headers - //lock (_response.headers_lock) - //{ - // if (_response.HeadersSent) - // return null; - // var ms = CreateNew(); - // _response.SendHeaders(closing, ms); - // return ms; - //} - - // SendHeaders works on shared headers - lock (_response._headersLock) - { - if (_response.SentHeaders) - { - return null; - } - - MemoryStream ms = new MemoryStream(); - _response.SendHeaders(closing, ms, isWebSocketHandshake); - return ms; - } - } - - private static byte[] s_crlf = new byte[] { 13, 10 }; - private static byte[] GetChunkSizeBytes(int size, bool final) - { - string str = string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""); - return Encoding.ASCII.GetBytes(str); - } - - internal void InternalWrite(byte[] buffer, int offset, int count) - { - if (_ignore_errors) - { - try - { - _stream.Write(buffer, offset, count); - } - catch { } - } - else - { - _stream.Write(buffer, offset, count); - } - } - - internal Task InternalWriteAsync(byte[] buffer, int offset, int count) => - _ignore_errors ? InternalWriteIgnoreErrorsAsync(buffer, offset, count) : _stream.WriteAsync(buffer, offset, count); - - private async Task InternalWriteIgnoreErrorsAsync(byte[] buffer, int offset, int count) - { - try { await _stream.WriteAsync(buffer, offset, count).ConfigureAwait(false); } - catch { } - } - - private void WriteCore(byte[] buffer, int offset, int size) - { - if (size == 0) - return; - - byte[] bytes = null; - MemoryStream ms = GetHeaders(false); - bool chunked = _response.SendChunked; - if (ms != null) - { - long start = ms.Position; // After the possible preamble for the encoding - ms.Position = ms.Length; - if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - ms.Write(bytes, 0, bytes.Length); - } - - int new_count = Math.Min(size, 16384 - (int)ms.Position + (int)start); - ms.Write(buffer, offset, new_count); - size -= new_count; - offset += new_count; - InternalWrite(ms.GetBuffer(), (int)start, (int)(ms.Length - start)); - ms.SetLength(0); - ms.Capacity = 0; // 'dispose' the buffer in ms. - } - else if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - InternalWrite(bytes, 0, bytes.Length); - } - - if (size > 0) - InternalWrite(buffer, offset, size); - if (chunked) - InternalWrite(s_crlf, 0, 2); - } - - private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback cback, object state) - { - if (_closed) - { - var ares = new HttpStreamAsyncResult(this); - ares._callback = cback; - ares._state = state; - ares.Complete(); - return ares; - } - - byte[] bytes = null; - MemoryStream ms = GetHeaders(false); - bool chunked = _response.SendChunked; - if (ms != null) - { - long start = ms.Position; - ms.Position = ms.Length; - if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - ms.Write(bytes, 0, bytes.Length); - } - ms.Write(buffer, offset, size); - buffer = ms.GetBuffer(); - offset = (int)start; - size = (int)(ms.Position - start); - } - else if (chunked) - { - bytes = GetChunkSizeBytes(size, false); - InternalWrite(bytes, 0, bytes.Length); - } - - return _stream.BeginWrite(buffer, offset, size, cback, state); - } - - private void EndWriteCore(IAsyncResult asyncResult) - { - if (_closed) - return; - - if (_ignore_errors) - { - try - { - _stream.EndWrite(asyncResult); - if (_response.SendChunked) - _stream.Write(s_crlf, 0, 2); - } - catch { } - } - else - { - _stream.EndWrite(asyncResult); - if (_response.SendChunked) - _stream.Write(s_crlf, 0, 2); - } - } - - public Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - return TransmitFileManaged(path, offset, count, fileShareMode, cancellationToken); - } - - const int StreamCopyToBufferSize = 81920; - private async Task TransmitFileManaged(string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken) - { - var allowAsync = _environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.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; - } - - var targetStream = this; - - if (count > 0) - { - await _streamHelper.CopyToAsync(fs, targetStream, count, cancellationToken).ConfigureAwait(false); - } - else - { - await fs.CopyToAsync(targetStream, StreamCopyToBufferSize, cancellationToken).ConfigureAwait(false); - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpResponseStream.cs b/SocketHttpListener/Net/HttpResponseStream.cs deleted file mode 100644 index 085c2ad0cb..0000000000 --- a/SocketHttpListener/Net/HttpResponseStream.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - internal sealed partial class HttpResponseStream : Stream - { - private bool _closed; - internal bool Closed => _closed; - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - - public override void Flush() { } - public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - - set => throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return base.BeginRead(buffer, offset, count, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return base.EndRead(asyncResult); - } - - public override void Write(byte[] buffer, int offset, int size) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - if (_closed) - { - return; - } - - WriteCore(buffer, offset, size); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - if (size < 0 || size > buffer.Length - offset) - { - throw new ArgumentOutOfRangeException(nameof(size)); - } - - return BeginWriteCore(buffer, offset, size, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult == null) - { - throw new ArgumentNullException(nameof(asyncResult)); - } - - EndWriteCore(asyncResult); - } - - protected override void Dispose(bool disposing) - { - try - { - if (disposing) - { - if (_closed) - { - return; - } - _closed = true; - DisposeCore(); - } - } - finally - { - base.Dispose(disposing); - } - } - } -} diff --git a/SocketHttpListener/Net/HttpStatusCode.cs b/SocketHttpListener/Net/HttpStatusCode.cs deleted file mode 100644 index d4bb61b8a7..0000000000 --- a/SocketHttpListener/Net/HttpStatusCode.cs +++ /dev/null @@ -1,321 +0,0 @@ -namespace SocketHttpListener.Net -{ - /// - /// Contains the values of the HTTP status codes. - /// - /// - /// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in - /// RFC 2616 for HTTP 1.1. - /// - public enum HttpStatusCode - { - /// - /// Equivalent to status code 100. - /// Indicates that the client should continue with its request. - /// - Continue = 100, - /// - /// Equivalent to status code 101. - /// Indicates that the server is switching the HTTP version or protocol on the connection. - /// - SwitchingProtocols = 101, - /// - /// Equivalent to status code 200. - /// Indicates that the client's request has succeeded. - /// - OK = 200, - /// - /// Equivalent to status code 201. - /// Indicates that the client's request has been fulfilled and resulted in a new resource being - /// created. - /// - Created = 201, - /// - /// Equivalent to status code 202. - /// Indicates that the client's request has been accepted for processing, but the processing - /// hasn't been completed. - /// - Accepted = 202, - /// - /// Equivalent to status code 203. - /// Indicates that the returned metainformation is from a local or a third-party copy instead of - /// the origin server. - /// - NonAuthoritativeInformation = 203, - /// - /// Equivalent to status code 204. - /// Indicates that the server has fulfilled the client's request but doesn't need to return - /// an entity-body. - /// - NoContent = 204, - /// - /// Equivalent to status code 205. - /// Indicates that the server has fulfilled the client's request, and the user agent should - /// reset the document view which caused the request to be sent. - /// - ResetContent = 205, - /// - /// Equivalent to status code 206. - /// Indicates that the server has fulfilled the partial GET request for the resource. - /// - PartialContent = 206, - /// - /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. - /// - /// - /// MultipleChoices is a synonym for Ambiguous. - /// - /// - MultipleChoices = 300, - /// - /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. - /// - /// - /// Ambiguous is a synonym for MultipleChoices. - /// - /// - Ambiguous = 300, - /// - /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. - /// - /// - /// MovedPermanently is a synonym for Moved. - /// - /// - MovedPermanently = 301, - /// - /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. - /// - /// - /// Moved is a synonym for MovedPermanently. - /// - /// - Moved = 301, - /// - /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// Found is a synonym for Redirect. - /// - /// - Found = 302, - /// - /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// Redirect is a synonym for Found. - /// - /// - Redirect = 302, - /// - /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. - /// - /// - /// SeeOther is a synonym for RedirectMethod. - /// - /// - SeeOther = 303, - /// - /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. - /// - /// - /// RedirectMethod is a synonym for SeeOther. - /// - /// - RedirectMethod = 303, - /// - /// Equivalent to status code 304. - /// Indicates that the client has performed a conditional GET request and access is allowed, - /// but the document hasn't been modified. - /// - NotModified = 304, - /// - /// Equivalent to status code 305. - /// Indicates that the requested resource must be accessed through the proxy given by - /// the Location field. - /// - UseProxy = 305, - /// - /// Equivalent to status code 306. - /// This status code was used in a previous version of the specification, is no longer used, - /// and is reserved for future use. - /// - Unused = 306, - /// - /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// TemporaryRedirect is a synonym for RedirectKeepVerb. - /// - /// - TemporaryRedirect = 307, - /// - /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. - /// - /// - /// RedirectKeepVerb is a synonym for TemporaryRedirect. - /// - /// - RedirectKeepVerb = 307, - /// - /// Equivalent to status code 400. - /// Indicates that the client's request couldn't be understood by the server due to - /// malformed syntax. - /// - BadRequest = 400, - /// - /// Equivalent to status code 401. - /// Indicates that the client's request requires user authentication. - /// - Unauthorized = 401, - /// - /// Equivalent to status code 402. - /// This status code is reserved for future use. - /// - PaymentRequired = 402, - /// - /// Equivalent to status code 403. - /// Indicates that the server understood the client's request but is refusing to fulfill it. - /// - Forbidden = 403, - /// - /// Equivalent to status code 404. - /// Indicates that the server hasn't found anything matching the request URI. - /// - NotFound = 404, - /// - /// Equivalent to status code 405. - /// Indicates that the method specified in the request line isn't allowed for the resource - /// identified by the request URI. - /// - MethodNotAllowed = 405, - /// - /// Equivalent to status code 406. - /// Indicates that the server doesn't have the appropriate resource to respond to the Accept - /// headers in the client's request. - /// - NotAcceptable = 406, - /// - /// Equivalent to status code 407. - /// Indicates that the client must first authenticate itself with the proxy. - /// - ProxyAuthenticationRequired = 407, - /// - /// Equivalent to status code 408. - /// Indicates that the client didn't produce a request within the time that the server was - /// prepared to wait. - /// - RequestTimeout = 408, - /// - /// Equivalent to status code 409. - /// Indicates that the client's request couldn't be completed due to a conflict on the server. - /// - Conflict = 409, - /// - /// Equivalent to status code 410. - /// Indicates that the requested resource is no longer available at the server and - /// no forwarding address is known. - /// - Gone = 410, - /// - /// Equivalent to status code 411. - /// Indicates that the server refuses to accept the client's request without a defined - /// Content-Length. - /// - LengthRequired = 411, - /// - /// Equivalent to status code 412. - /// Indicates that the precondition given in one or more of the request headers evaluated to - /// false when it was tested on the server. - /// - PreconditionFailed = 412, - /// - /// Equivalent to status code 413. - /// Indicates that the entity of the client's request is larger than the server is willing or - /// able to process. - /// - RequestEntityTooLarge = 413, - /// - /// Equivalent to status code 414. - /// Indicates that the request URI is longer than the server is willing to interpret. - /// - RequestUriTooLong = 414, - /// - /// Equivalent to status code 415. - /// Indicates that the entity of the client's request is in a format not supported by - /// the requested resource for the requested method. - /// - UnsupportedMediaType = 415, - /// - /// Equivalent to status code 416. - /// Indicates that none of the range specifier values in a Range request header overlap - /// the current extent of the selected resource. - /// - RequestedRangeNotSatisfiable = 416, - /// - /// Equivalent to status code 417. - /// Indicates that the expectation given in an Expect request header couldn't be met by - /// the server. - /// - ExpectationFailed = 417, - /// - /// Equivalent to status code 500. - /// Indicates that the server encountered an unexpected condition which prevented it from - /// fulfilling the client's request. - /// - InternalServerError = 500, - /// - /// Equivalent to status code 501. - /// Indicates that the server doesn't support the functionality required to fulfill the client's - /// request. - /// - NotImplemented = 501, - /// - /// Equivalent to status code 502. - /// Indicates that a gateway or proxy server received an invalid response from the upstream - /// server. - /// - BadGateway = 502, - /// - /// Equivalent to status code 503. - /// Indicates that the server is currently unable to handle the client's request due to - /// a temporary overloading or maintenance of the server. - /// - ServiceUnavailable = 503, - /// - /// Equivalent to status code 504. - /// Indicates that a gateway or proxy server didn't receive a timely response from the upstream - /// server or some other auxiliary server. - /// - GatewayTimeout = 504, - /// - /// Equivalent to status code 505. - /// Indicates that the server doesn't support the HTTP version used in the client's request. - /// - HttpVersionNotSupported = 505, - } -} diff --git a/SocketHttpListener/Net/HttpStatusDescription.cs b/SocketHttpListener/Net/HttpStatusDescription.cs deleted file mode 100644 index a4e42560b9..0000000000 --- a/SocketHttpListener/Net/HttpStatusDescription.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static class HttpStatusDescription - { - internal static string Get(HttpStatusCode code) - { - return Get((int)code); - } - - internal static string Get(int code) - { - switch (code) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-Uri Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 422: return "Unprocessable Entity"; - case 423: return "Locked"; - case 424: return "Failed Dependency"; - case 426: return "Upgrade Required"; // RFC 2817 - - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "Http Version Not Supported"; - case 507: return "Insufficient Storage"; - } - return null; - } - } -} diff --git a/SocketHttpListener/Net/HttpStreamAsyncResult.cs b/SocketHttpListener/Net/HttpStreamAsyncResult.cs deleted file mode 100644 index 46944c624a..0000000000 --- a/SocketHttpListener/Net/HttpStreamAsyncResult.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net -{ - internal class HttpStreamAsyncResult : IAsyncResult - { - private object _locker = new object(); - private ManualResetEvent _handle; - private bool _completed; - - internal readonly object _parent; - internal byte[] _buffer; - internal int _offset; - internal int _count; - internal AsyncCallback _callback; - internal object _state; - internal int _synchRead; - internal Exception _error; - internal bool _endCalled; - - internal HttpStreamAsyncResult(object parent) - { - _parent = parent; - } - - public void Complete(Exception e) - { - _error = e; - Complete(); - } - - public void Complete() - { - lock (_locker) - { - if (_completed) - return; - - _completed = true; - if (_handle != null) - _handle.Set(); - - if (_callback != null) - Task.Run(() => _callback(this)); - } - } - - public object AsyncState => _state; - - public WaitHandle AsyncWaitHandle - { - get - { - lock (_locker) - { - if (_handle == null) - _handle = new ManualResetEvent(_completed); - } - - return _handle; - } - } - - public bool CompletedSynchronously => false; - - public bool IsCompleted - { - get - { - lock (_locker) - { - return _completed; - } - } - } - } -} diff --git a/SocketHttpListener/Net/HttpVersion.cs b/SocketHttpListener/Net/HttpVersion.cs deleted file mode 100644 index c0839b46d5..0000000000 --- a/SocketHttpListener/Net/HttpVersion.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace SocketHttpListener.Net -{ - // - // - public class HttpVersion - { - - public static readonly Version Version10 = new Version(1, 0); - public static readonly Version Version11 = new Version(1, 1); - - // pretty useless.. - public HttpVersion() { } - } -} diff --git a/SocketHttpListener/Net/ListenerPrefix.cs b/SocketHttpListener/Net/ListenerPrefix.cs deleted file mode 100644 index edfcb89043..0000000000 --- a/SocketHttpListener/Net/ListenerPrefix.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.Net; - -namespace SocketHttpListener.Net -{ - internal sealed class ListenerPrefix - { - private string _original; - private string _host; - private ushort _port; - private string _path; - private bool _secure; - private IPAddress[] _addresses; - internal HttpListener _listener; - - public ListenerPrefix(string prefix) - { - _original = prefix; - Parse(prefix); - } - - public override string ToString() - { - return _original; - } - - public IPAddress[] Addresses - { - get => _addresses; - set => _addresses = value; - } - public bool Secure => _secure; - - public string Host => _host; - - public int Port => _port; - - public string Path => _path; - - // Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection. - public override bool Equals(object o) - { - var other = o as ListenerPrefix; - if (other == null) - return false; - - return (_original == other._original); - } - - public override int GetHashCode() - { - return _original.GetHashCode(); - } - - private void Parse(string uri) - { - ushort default_port = 80; - if (uri.StartsWith("https://")) - { - default_port = 443; - _secure = true; - } - - int length = uri.Length; - int start_host = uri.IndexOf(':') + 3; - if (start_host >= length) - throw new ArgumentException("net_listener_host"); - - int colon = uri.IndexOf(':', start_host, length - start_host); - int root; - if (colon > 0) - { - _host = uri.Substring(start_host, colon - start_host); - root = uri.IndexOf('/', colon, length - colon); - _port = (ushort)int.Parse(uri.Substring(colon + 1, root - colon - 1)); - _path = uri.Substring(root); - } - else - { - root = uri.IndexOf('/', start_host, length - start_host); - _host = uri.Substring(start_host, root - start_host); - _port = default_port; - _path = uri.Substring(root); - } - if (_path.Length != 1) - _path = _path.Substring(0, _path.Length - 1); - } - } -} diff --git a/SocketHttpListener/Net/UriScheme.cs b/SocketHttpListener/Net/UriScheme.cs deleted file mode 100644 index 33d1f09db6..0000000000 --- a/SocketHttpListener/Net/UriScheme.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace SocketHttpListener.Net -{ - internal static class UriScheme - { - public const string File = "file"; - public const string Ftp = "ftp"; - public const string Gopher = "gopher"; - public const string Http = "http"; - public const string Https = "https"; - public const string News = "news"; - public const string NetPipe = "net.pipe"; - public const string NetTcp = "net.tcp"; - public const string Nntp = "nntp"; - public const string Mailto = "mailto"; - public const string Ws = "ws"; - public const string Wss = "wss"; - - public const string SchemeDelimiter = "://"; - } -} diff --git a/SocketHttpListener/Net/WebHeaderCollection.cs b/SocketHttpListener/Net/WebHeaderCollection.cs deleted file mode 100644 index 34fca808be..0000000000 --- a/SocketHttpListener/Net/WebHeaderCollection.cs +++ /dev/null @@ -1,360 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net -{ - [ComVisible(true)] - public class WebHeaderCollection : QueryParamCollection - { - [Flags] - internal enum HeaderInfo - { - Request = 1, - Response = 1 << 1, - MultiValue = 1 << 10 - } - - static readonly bool[] allowed_chars = { - false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, false, false, false, false, false, false, false, false, false, - false, false, false, false, false, true, false, true, true, true, true, false, false, false, true, - true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false, - false, false, false, false, false, false, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, - true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, - false, true, false - }; - - static readonly Dictionary headers; - - static WebHeaderCollection() - { - headers = new Dictionary(StringComparer.OrdinalIgnoreCase) { - { "Allow", HeaderInfo.MultiValue }, - { "Accept", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Accept-Charset", HeaderInfo.MultiValue }, - { "Accept-Encoding", HeaderInfo.MultiValue }, - { "Accept-Language", HeaderInfo.MultiValue }, - { "Accept-Ranges", HeaderInfo.MultiValue }, - { "Age", HeaderInfo.Response }, - { "Authorization", HeaderInfo.MultiValue }, - { "Cache-Control", HeaderInfo.MultiValue }, - { "Cookie", HeaderInfo.MultiValue }, - { "Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Content-Encoding", HeaderInfo.MultiValue }, - { "Content-Length", HeaderInfo.Request | HeaderInfo.Response }, - { "Content-Type", HeaderInfo.Request }, - { "Content-Language", HeaderInfo.MultiValue }, - { "Date", HeaderInfo.Request }, - { "Expect", HeaderInfo.Request | HeaderInfo.MultiValue}, - { "Host", HeaderInfo.Request }, - { "If-Match", HeaderInfo.MultiValue }, - { "If-Modified-Since", HeaderInfo.Request }, - { "If-None-Match", HeaderInfo.MultiValue }, - { "Keep-Alive", HeaderInfo.Response }, - { "Pragma", HeaderInfo.MultiValue }, - { "Proxy-Authenticate", HeaderInfo.MultiValue }, - { "Proxy-Authorization", HeaderInfo.MultiValue }, - { "Proxy-Connection", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Range", HeaderInfo.Request | HeaderInfo.MultiValue }, - { "Referer", HeaderInfo.Request }, - { "Set-Cookie", HeaderInfo.MultiValue }, - { "Set-Cookie2", HeaderInfo.MultiValue }, - { "Server", HeaderInfo.Response }, - { "TE", HeaderInfo.MultiValue }, - { "Trailer", HeaderInfo.MultiValue }, - { "Transfer-Encoding", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo.MultiValue }, - { "Translate", HeaderInfo.Request | HeaderInfo.Response }, - { "Upgrade", HeaderInfo.MultiValue }, - { "User-Agent", HeaderInfo.Request }, - { "Vary", HeaderInfo.MultiValue }, - { "Via", HeaderInfo.MultiValue }, - { "Warning", HeaderInfo.MultiValue }, - { "WWW-Authenticate", HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketAccept", HeaderInfo.Response }, - { "SecWebSocketExtensions", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketKey", HeaderInfo.Request }, - { "Sec-WebSocket-Protocol", HeaderInfo.Request | HeaderInfo.Response | HeaderInfo. MultiValue }, - { "SecWebSocketVersion", HeaderInfo.Response | HeaderInfo. MultiValue } - }; - } - - // Methods - - public void Add(string header) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - int pos = header.IndexOf(':'); - if (pos == -1) - throw new ArgumentException("no colon found", nameof(header)); - - this.Add(header.Substring(0, pos), header.Substring(pos + 1)); - } - - public override void Add(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - - this.AddWithoutValidate(name, value); - } - - protected void AddWithoutValidate(string headerName, string headerValue) - { - if (!IsHeaderName(headerName)) - throw new ArgumentException("invalid header name: " + headerName, nameof(headerName)); - if (headerValue == null) - headerValue = string.Empty; - else - headerValue = headerValue.Trim(); - if (!IsHeaderValue(headerValue)) - throw new ArgumentException("invalid header value: " + headerValue, nameof(headerValue)); - - AddValue(headerName, headerValue); - } - - internal void AddValue(string headerName, string headerValue) - { - base.Add(headerName, headerValue); - } - - internal List GetValues_internal(string header, bool split) - { - if (header == null) - throw new ArgumentNullException(nameof(header)); - - var values = base.GetValues(header); - if (values == null || values.Count == 0) - return null; - - if (split && IsMultiValue(header)) - { - List separated = null; - foreach (var value in values) - { - if (value.IndexOf(',') < 0) - { - if (separated != null) - separated.Add(value); - - continue; - } - - if (separated == null) - { - separated = new List(values.Count + 1); - foreach (var v in values) - { - if (v == value) - break; - - separated.Add(v); - } - } - - var slices = value.Split(','); - var slices_length = slices.Length; - if (value[value.Length - 1] == ',') - --slices_length; - - for (int i = 0; i < slices_length; ++i) - { - separated.Add(slices[i].Trim()); - } - } - - if (separated != null) - return separated; - } - - return values; - } - - public override List GetValues(string header) - { - return GetValues_internal(header, true); - } - - public override string[] GetValues(int index) - { - string[] values = base.GetValues(index); - - if (values == null || values.Length == 0) - { - return null; - } - - return values; - } - - public static bool IsRestricted(string headerName) - { - return IsRestricted(headerName, false); - } - - public static bool IsRestricted(string headerName, bool response) - { - if (headerName == null) - throw new ArgumentNullException(nameof(headerName)); - - if (headerName.Length == 0) - throw new ArgumentException("empty string", nameof(headerName)); - - if (!IsHeaderName(headerName)) - throw new ArgumentException("Invalid character in header"); - - if (!headers.TryGetValue(headerName, out HeaderInfo info)) - return false; - - var flag = response ? HeaderInfo.Response : HeaderInfo.Request; - return (info & flag) != 0; - } - - public override void Set(string name, string value) - { - if (name == null) - throw new ArgumentNullException(nameof(name)); - if (!IsHeaderName(name)) - throw new ArgumentException("invalid header name"); - if (value == null) - value = string.Empty; - else - value = value.Trim(); - if (!IsHeaderValue(value)) - throw new ArgumentException("invalid header value"); - - base.Set(name, value); - } - - internal string ToStringMultiValue() - { - var sb = new StringBuilder(); - - int count = base.Count; - for (int i = 0; i < count; i++) - { - string key = GetKey(i); - if (IsMultiValue(key)) - { - foreach (string v in GetValues(i)) - { - sb.Append(key) - .Append(": ") - .Append(v) - .Append("\r\n"); - } - } - else - { - sb.Append(key) - .Append(": ") - .Append(Get(i)) - .Append("\r\n"); - } - } - return sb.Append("\r\n").ToString(); - } - - public override string ToString() - { - var sb = new StringBuilder(); - - int count = base.Count; - for (int i = 0; i < count; i++) - sb.Append(GetKey(i)) - .Append(": ") - .Append(Get(i)) - .Append("\r\n"); - - return sb.Append("\r\n").ToString(); - } - - - // Internal Methods - - // With this we don't check for invalid characters in header. See bug #55994. - internal void SetInternal(string header) - { - int pos = header.IndexOf(':'); - if (pos == -1) - throw new ArgumentException("no colon found", nameof(header)); - - SetInternal(header.Substring(0, pos), header.Substring(pos + 1)); - } - - internal void SetInternal(string name, string value) - { - if (value == null) - value = string.Empty; - else - value = value.Trim(); - if (!IsHeaderValue(value)) - throw new ArgumentException("invalid header value"); - - if (IsMultiValue(name)) - { - base.Add(name, value); - } - else - { - base.Remove(name); - base.Set(name, value); - } - } - - internal static bool IsMultiValue(string headerName) - { - if (headerName == null) - return false; - - return headers.TryGetValue(headerName, out HeaderInfo info) && (info & HeaderInfo.MultiValue) != 0; - } - - internal static bool IsHeaderValue(string value) - { - // TEXT any 8 bit value except CTL's (0-31 and 127) - // but including \r\n space and \t - // after a newline at least one space or \t must follow - // certain header fields allow comments () - - int len = value.Length; - for (int i = 0; i < len; i++) - { - char c = value[i]; - if (c == 127) - return false; - if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t')) - return false; - if (c == '\n' && ++i < len) - { - c = value[i]; - if (c != ' ' && c != '\t') - return false; - } - } - - return true; - } - - internal static bool IsHeaderName(string name) - { - if (name == null || name.Length == 0) - return false; - - int len = name.Length; - for (int i = 0; i < len; i++) - { - char c = name[i]; - if (c > 126 || !allowed_chars[c]) - return false; - } - - return true; - } - } -} diff --git a/SocketHttpListener/Net/WebHeaderEncoding.cs b/SocketHttpListener/Net/WebHeaderEncoding.cs deleted file mode 100644 index 96e0cc85d8..0000000000 --- a/SocketHttpListener/Net/WebHeaderEncoding.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Text; - -namespace SocketHttpListener.Net -{ - // we use this static class as a helper class to encode/decode HTTP headers. - // what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF - // and a byte in the range 0x00-0xFF (which is the range that can hit the network). - // The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow. - // It doesn't work for string -> byte[] because of best-fit-mapping problems. - internal static class WebHeaderEncoding - { - // We don't want '?' replacement characters, just fail. - private static readonly Encoding s_utf8Decoder = Encoding.GetEncoding("utf-8", EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); - - internal static unsafe string GetString(byte[] bytes, int byteIndex, int byteCount) - { - fixed (byte* pBytes = bytes) - return GetString(pBytes + byteIndex, byteCount); - } - - internal static unsafe string GetString(byte* pBytes, int byteCount) - { - if (byteCount < 1) - return ""; - - string s = new string('\0', byteCount); - - fixed (char* pStr = s) - { - char* pString = pStr; - while (byteCount >= 8) - { - pString[0] = (char)pBytes[0]; - pString[1] = (char)pBytes[1]; - pString[2] = (char)pBytes[2]; - pString[3] = (char)pBytes[3]; - pString[4] = (char)pBytes[4]; - pString[5] = (char)pBytes[5]; - pString[6] = (char)pBytes[6]; - pString[7] = (char)pBytes[7]; - pString += 8; - pBytes += 8; - byteCount -= 8; - } - for (int i = 0; i < byteCount; i++) - { - pString[i] = (char)pBytes[i]; - } - } - - return s; - } - - internal static int GetByteCount(string myString) - { - return myString.Length; - } - internal static unsafe void GetBytes(string myString, int charIndex, int charCount, byte[] bytes, int byteIndex) - { - if (myString.Length == 0) - { - return; - } - fixed (byte* bufferPointer = bytes) - { - byte* newBufferPointer = bufferPointer + byteIndex; - int finalIndex = charIndex + charCount; - while (charIndex < finalIndex) - { - *newBufferPointer++ = (byte)myString[charIndex++]; - } - } - } - internal static byte[] GetBytes(string myString) - { - byte[] bytes = new byte[myString.Length]; - if (myString.Length != 0) - { - GetBytes(myString, 0, myString.Length, bytes, 0); - } - return bytes; - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs deleted file mode 100644 index 5ed49ec47d..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Principal; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net.WebSockets -{ - public class HttpListenerWebSocketContext : WebSocketContext - { - private readonly Uri _requestUri; - private readonly QueryParamCollection _headers; - private readonly CookieCollection _cookieCollection; - private readonly IPrincipal _user; - private readonly bool _isAuthenticated; - private readonly bool _isLocal; - private readonly bool _isSecureConnection; - - private readonly string _origin; - private readonly IEnumerable _secWebSocketProtocols; - private readonly string _secWebSocketVersion; - private readonly string _secWebSocketKey; - - private readonly WebSocket _webSocket; - - internal HttpListenerWebSocketContext( - Uri requestUri, - QueryParamCollection headers, - CookieCollection cookieCollection, - IPrincipal user, - bool isAuthenticated, - bool isLocal, - bool isSecureConnection, - string origin, - IEnumerable secWebSocketProtocols, - string secWebSocketVersion, - string secWebSocketKey, - WebSocket webSocket) - { - _cookieCollection = new CookieCollection(); - _cookieCollection.Add(cookieCollection); - - //_headers = new NameValueCollection(headers); - _headers = headers; - _user = CopyPrincipal(user); - - _requestUri = requestUri; - _isAuthenticated = isAuthenticated; - _isLocal = isLocal; - _isSecureConnection = isSecureConnection; - _origin = origin; - _secWebSocketProtocols = secWebSocketProtocols; - _secWebSocketVersion = secWebSocketVersion; - _secWebSocketKey = secWebSocketKey; - _webSocket = webSocket; - } - - public override Uri RequestUri => _requestUri; - - public override QueryParamCollection Headers => _headers; - - public override string Origin => _origin; - - public override IEnumerable SecWebSocketProtocols => _secWebSocketProtocols; - - public override string SecWebSocketVersion => _secWebSocketVersion; - - public override string SecWebSocketKey => _secWebSocketKey; - - public override CookieCollection CookieCollection => _cookieCollection; - - public override IPrincipal User => _user; - - public override bool IsAuthenticated => _isAuthenticated; - - public override bool IsLocal => _isLocal; - - public override bool IsSecureConnection => _isSecureConnection; - - public override WebSocket WebSocket => _webSocket; - - private static IPrincipal CopyPrincipal(IPrincipal user) - { - if (user != null) - { - throw new NotImplementedException(); - } - - return null; - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs deleted file mode 100644 index 1cfd2dc902..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class HttpWebSocket - { - private const string SupportedVersion = "13"; - - internal static async Task AcceptWebSocketAsyncCore(HttpListenerContext context, - string subProtocol, - int receiveBufferSize, - TimeSpan keepAliveInterval, - ArraySegment? internalBuffer = null) - { - ValidateOptions(subProtocol, receiveBufferSize, MinSendBufferSize, keepAliveInterval); - - // get property will create a new response if one doesn't exist. - HttpListenerResponse response = context.Response; - HttpListenerRequest request = context.Request; - ValidateWebSocketHeaders(context); - - string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - - // Optional for non-browser client - string origin = request.Headers[HttpKnownHeaderNames.Origin]; - - string[] secWebSocketProtocols = null; - bool shouldSendSecWebSocketProtocolHeader = - ProcessWebSocketProtocolHeader( - request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol], - subProtocol, - out var outgoingSecWebSocketProtocolString); - - if (shouldSendSecWebSocketProtocolHeader) - { - secWebSocketProtocols = new string[] { outgoingSecWebSocketProtocolString }; - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, outgoingSecWebSocketProtocolString); - } - - // negotiate the websocket key return value - string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - string secWebSocketAccept = HttpWebSocket.GetSecWebSocketAcceptString(secWebSocketKey); - - response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade); - response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketUpgradeToken); - response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept); - - response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101 - response.StatusDescription = HttpStatusDescription.Get(HttpStatusCode.SwitchingProtocols); - - var responseStream = response.OutputStream as HttpResponseStream; - - // Send websocket handshake headers - await responseStream.WriteWebSocketHandshakeHeadersAsync().ConfigureAwait(false); - - //WebSocket webSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval); - var webSocket = new WebSocket(subProtocol); - - var webSocketContext = new HttpListenerWebSocketContext( - request.Url, - request.Headers, - request.Cookies, - context.User, - request.IsAuthenticated, - request.IsLocal, - request.IsSecureConnection, - origin, - secWebSocketProtocols != null ? secWebSocketProtocols : Array.Empty(), - secWebSocketVersion, - secWebSocketKey, - webSocket); - - webSocket.SetContext(webSocketContext, context.Connection.Close, context.Connection.Stream); - - return webSocketContext; - } - - private const bool WebSocketsSupported = true; - } -} diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs deleted file mode 100644 index b346cc98ec..0000000000 --- a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Security.Cryptography; -using System.Text; -using System.Threading; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class HttpWebSocket - { - internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - internal const string WebSocketUpgradeToken = "websocket"; - internal const int DefaultReceiveBufferSize = 16 * 1024; - internal const int DefaultClientSendBufferSize = 16 * 1024; - - [SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 used only for hashing purposes, not for crypto.")] - internal static string GetSecWebSocketAcceptString(string secWebSocketKey) - { - string retVal; - - // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. - using (var sha1 = SHA1.Create()) - { - string acceptString = string.Concat(secWebSocketKey, HttpWebSocket.SecWebSocketKeyGuid); - byte[] toHash = Encoding.UTF8.GetBytes(acceptString); - retVal = Convert.ToBase64String(sha1.ComputeHash(toHash)); - } - - return retVal; - } - - // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server. - internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol, - string subProtocol, - out string acceptProtocol) - { - acceptProtocol = string.Empty; - if (string.IsNullOrEmpty(clientSecWebSocketProtocol)) - { - // client hasn't specified any Sec-WebSocket-Protocol header - if (subProtocol != null) - { - // If the server specified _anything_ this isn't valid. - throw new WebSocketException("UnsupportedProtocol"); - } - // Treat empty and null from the server as the same thing here, server should not send headers. - return false; - } - - // here, we know the client specified something and it's non-empty. - - if (subProtocol == null) - { - // client specified some protocols, server specified 'null'. So server should send headers. - return true; - } - - // here, we know that the client has specified something, it's not empty - // and the server has specified exactly one protocol - - string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' }, - StringSplitOptions.RemoveEmptyEntries); - acceptProtocol = subProtocol; - - // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that - // this exists in the list the client specified. - for (int i = 0; i < requestProtocols.Length; i++) - { - string currentRequestProtocol = requestProtocols[i].Trim(); - if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase)) - { - return true; - } - } - - throw new WebSocketException("net_WebSockets_AcceptUnsupportedProtocol"); - } - - internal static void ValidateOptions(string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval) - { - if (subProtocol != null) - { - WebSocketValidate.ValidateSubprotocol(subProtocol); - } - - if (receiveBufferSize < MinReceiveBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too small."); - } - - if (sendBufferSize < MinSendBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too small."); - } - - if (receiveBufferSize > MaxBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(receiveBufferSize), "The receiveBufferSize was too large."); - } - - if (sendBufferSize > MaxBufferSize) - { - throw new ArgumentOutOfRangeException(nameof(sendBufferSize), "The sendBufferSize was too large."); - } - - if (keepAliveInterval < Timeout.InfiniteTimeSpan) // -1 millisecond - { - throw new ArgumentOutOfRangeException(nameof(keepAliveInterval), "The keepAliveInterval was too small."); - } - } - - internal const int MinSendBufferSize = 16; - internal const int MinReceiveBufferSize = 256; - internal const int MaxBufferSize = 64 * 1024; - - private static void ValidateWebSocketHeaders(HttpListenerContext context) - { - if (!WebSocketsSupported) - { - throw new PlatformNotSupportedException("net_WebSockets_UnsupportedPlatform"); - } - - if (!context.Request.IsWebSocketRequest) - { - throw new WebSocketException("net_WebSockets_AcceptNotAWebSocket"); - } - - string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; - if (string.IsNullOrEmpty(secWebSocketVersion)) - { - throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound"); - } - - if (!string.Equals(secWebSocketVersion, SupportedVersion, StringComparison.OrdinalIgnoreCase)) - { - throw new WebSocketException("net_WebSockets_AcceptUnsupportedWebSocketVersion"); - } - - string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey]; - bool isSecWebSocketKeyInvalid = string.IsNullOrWhiteSpace(secWebSocketKey); - if (!isSecWebSocketKeyInvalid) - { - try - { - // key must be 16 bytes then base64-encoded - isSecWebSocketKeyInvalid = Convert.FromBase64String(secWebSocketKey).Length != 16; - } - catch - { - isSecWebSocketKeyInvalid = true; - } - } - if (isSecWebSocketKeyInvalid) - { - throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound"); - } - } - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs b/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs deleted file mode 100644 index 5ac89cf480..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace SocketHttpListener.Net.WebSockets -{ - public enum WebSocketCloseStatus - { - NormalClosure = 1000, - EndpointUnavailable = 1001, - ProtocolError = 1002, - InvalidMessageType = 1003, - Empty = 1005, - // AbnormalClosure = 1006, // 1006 is reserved and should never be used by user - InvalidPayloadData = 1007, - PolicyViolation = 1008, - MessageTooBig = 1009, - MandatoryExtension = 1010, - InternalServerError = 1011 - // TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user - - // 0 - 999 Status codes in the range 0-999 are not used. - // 1000 - 1999 Status codes in the range 1000-1999 are reserved for definition by this protocol. - // 2000 - 2999 Status codes in the range 2000-2999 are reserved for use by extensions. - // 3000 - 3999 Status codes in the range 3000-3999 MAY be used by libraries and frameworks. The - // interpretation of these codes is undefined by this protocol. End applications MUST - // NOT use status codes in this range. - // 4000 - 4999 Status codes in the range 4000-4999 MAY be used by application code. The interpretation - // of these codes is undefined by this protocol. - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs deleted file mode 100644 index 10ad864398..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Principal; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener.Net.WebSockets -{ - public abstract class WebSocketContext - { - public abstract Uri RequestUri { get; } - public abstract QueryParamCollection Headers { get; } - public abstract string Origin { get; } - public abstract IEnumerable SecWebSocketProtocols { get; } - public abstract string SecWebSocketVersion { get; } - public abstract string SecWebSocketKey { get; } - public abstract CookieCollection CookieCollection { get; } - public abstract IPrincipal User { get; } - public abstract bool IsAuthenticated { get; } - public abstract bool IsLocal { get; } - public abstract bool IsSecureConnection { get; } - public abstract WebSocket WebSocket { get; } - } -} diff --git a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs b/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs deleted file mode 100644 index 0469e3b6c5..0000000000 --- a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Globalization; -using System.Text; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener.Net.WebSockets -{ - internal static partial class WebSocketValidate - { - internal const int MaxControlFramePayloadLength = 123; - private const int CloseStatusCodeAbort = 1006; - private const int CloseStatusCodeFailedTLSHandshake = 1015; - private const int InvalidCloseStatusCodesFrom = 0; - private const int InvalidCloseStatusCodesTo = 999; - private const string Separators = "()<>@,;:\\\"/[]?={} "; - - internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates) - { - string validStatesText = string.Empty; - - if (validStates != null && validStates.Length > 0) - { - foreach (WebSocketState validState in validStates) - { - if (currentState == validState) - { - // Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior. - if (isDisposed) - { - throw new ObjectDisposedException(nameof(WebSocket)); - } - - return; - } - } - - validStatesText = string.Join(", ", validStates); - } - - throw new WebSocketException("net_WebSockets_InvalidState"); - } - - internal static void ValidateSubprotocol(string subProtocol) - { - if (string.IsNullOrWhiteSpace(subProtocol)) - { - throw new ArgumentException("net_WebSockets_InvalidEmptySubProtocol"); - } - - string invalidChar = null; - int i = 0; - while (i < subProtocol.Length) - { - char ch = subProtocol[i]; - if (ch < 0x21 || ch > 0x7e) - { - invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch); - break; - } - - if (!char.IsLetterOrDigit(ch) && - Separators.IndexOf(ch) >= 0) - { - invalidChar = ch.ToString(); - break; - } - - i++; - } - - if (invalidChar != null) - { - throw new ArgumentException("net_WebSockets_InvalidCharInProtocolString"); - } - } - - internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription) - { - if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription)) - { - throw new ArgumentException("net_WebSockets_ReasonNotNull"); - } - - int closeStatusCode = (int)closeStatus; - - if ((closeStatusCode >= InvalidCloseStatusCodesFrom && - closeStatusCode <= InvalidCloseStatusCodesTo) || - closeStatusCode == CloseStatusCodeAbort || - closeStatusCode == CloseStatusCodeFailedTLSHandshake) - { - // CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort - throw new ArgumentException("net_WebSockets_InvalidCloseStatusCode"); - } - - int length = 0; - if (!string.IsNullOrEmpty(statusDescription)) - { - length = Encoding.UTF8.GetByteCount(statusDescription); - } - - if (length > MaxControlFramePayloadLength) - { - throw new ArgumentException("net_WebSockets_InvalidCloseStatusDescription"); - } - } - - internal static void ValidateArraySegment(ArraySegment arraySegment, string parameterName) - { - if (arraySegment.Array == null) - { - throw new ArgumentNullException(parameterName + "." + nameof(arraySegment.Array)); - } - if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length) - { - throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Offset)); - } - if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset)) - { - throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Count)); - } - } - - internal static void ValidateBuffer(byte[] buffer, int offset, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - - if (offset < 0 || offset > buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(offset)); - } - - if (count < 0 || count > (buffer.Length - offset)) - { - throw new ArgumentOutOfRangeException(nameof(count)); - } - } - } -} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs index 0dcb6a64bc..afce871fd6 100644 --- a/SocketHttpListener/WebSocket.cs +++ b/SocketHttpListener/WebSocket.cs @@ -4,11 +4,10 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Sockets; +using System.Net.WebSockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using SocketHttpListener.Net.WebSockets; -using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode; using WebSocketState = System.Net.WebSockets.WebSocketState; namespace SocketHttpListener From 33b67a357fe73103f046b822d7cc66fc5318a9ef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 21 Feb 2019 12:32:47 +0100 Subject: [PATCH 032/166] Remove unused deps --- Jellyfin.Server/CoreAppHost.cs | 11 +------ .../SocketSharp/WebSocketSharpListener.cs | 32 +------------------ 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 84d78d3fb6..68fd2453e3 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -47,15 +47,6 @@ namespace Jellyfin.Server protected override void ShutdownInternal() => Program.Shutdown(); protected override IHttpListener CreateHttpListener() - => new WebSocketSharpListener( - Logger, - Certificate, - StreamHelper, - NetworkManager, - SocketFactory, - CryptographyProvider, - SupportsDualModeSockets, - FileSystemManager, - EnvironmentInfo); + => new WebSocketSharpListener(Logger); } } diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index 64856f04af..c458fdb78f 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,18 +1,12 @@ using System; using System.Collections.Generic; using System.Net; - using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.Net; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace Jellyfin.Server.SocketSharp @@ -22,38 +16,14 @@ using Microsoft.Extensions.Logging; private HttpListener _listener; private readonly ILogger _logger; - private readonly X509Certificate _certificate; - private readonly IStreamHelper _streamHelper; - private readonly INetworkManager _networkManager; - private readonly ISocketFactory _socketFactory; - private readonly ICryptoProvider _cryptoProvider; - private readonly IFileSystem _fileSystem; - private readonly bool _enableDualMode; - private readonly IEnvironmentInfo _environment; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); private CancellationToken _disposeCancellationToken; public WebSocketSharpListener( - ILogger logger, - X509Certificate certificate, - IStreamHelper streamHelper, - INetworkManager networkManager, - ISocketFactory socketFactory, - ICryptoProvider cryptoProvider, - bool enableDualMode, - IFileSystem fileSystem, - IEnvironmentInfo environment) + ILogger logger) { _logger = logger; - _certificate = certificate; - _streamHelper = streamHelper; - _networkManager = networkManager; - _socketFactory = socketFactory; - _cryptoProvider = cryptoProvider; - _enableDualMode = enableDualMode; - _fileSystem = fileSystem; - _environment = environment; _disposeCancellationToken = _disposeCancellationTokenSource.Token; } From 852460b99155e015ed5f1d7ad2fab0281bfdfbec Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 25 Feb 2019 23:34:32 +0100 Subject: [PATCH 033/166] kestrel init --- .../ApplicationHost.cs | 119 ++- .../Emby.Server.Implementations.csproj | 10 + .../HttpServer/HttpListenerHost.cs | 2 +- .../SocketSharp/HttpFile.cs | 18 + .../SocketSharp/HttpPostedFile.cs | 204 ++++++ .../SocketSharp/RequestMono.cs | 681 ++++++++++++++++++ .../SocketSharp/SharpWebSocket.cs | 149 ++++ .../SocketSharp/WebSocketSharpListener.cs | 261 +++++++ .../SocketSharp/WebSocketSharpRequest.cs | 539 ++++++++++++++ .../SocketSharp/WebSocketSharpResponse.cs | 206 ++++++ Emby.Server.Implementations/Startup.cs | 34 + Jellyfin.Server/Program.cs | 3 +- MediaBrowser.Model/MediaBrowser.Model.csproj | 4 +- .../Services/QueryParamCollection.cs | 17 + 14 files changed, 2242 insertions(+), 5 deletions(-) create mode 100644 Emby.Server.Implementations/SocketSharp/HttpFile.cs create mode 100644 Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs create mode 100644 Emby.Server.Implementations/SocketSharp/RequestMono.cs create mode 100644 Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs create mode 100644 Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs create mode 100644 Emby.Server.Implementations/Startup.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 45a819f260..5e8611d531 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -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; @@ -105,10 +106,15 @@ 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.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using ServiceStack; +using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -123,7 +129,7 @@ namespace Emby.Server.Implementations /// /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - + public IWebHost Host { get; set; } public virtual bool CanLaunchWebBrowser { get @@ -612,6 +618,115 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection); FindParts(); + + Host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot("/Users/clausvium/RiderProjects/jellyfin/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin-web/src") + .UseStartup() +// .ConfigureServices(async services => +// { +// services.AddSingleton(startUp); +// RegisterResources(services); +// FindParts(); +// try +// { +// ImageProcessor.ImageEncoder = +// new NullImageEncoder(); //SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); +// } +// catch (Exception ex) +// { +// Logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); +// ImageProcessor.ImageEncoder = new NullImageEncoder(); +// } +// await RunStartupTasks().ConfigureAwait(false); +// }) + .UseUrls("http://localhost:8096") + .ConfigureServices(s => s.AddRouting()) + .Configure( app => + { + app.UseWebSockets(new WebSocketOptions { + KeepAliveInterval = TimeSpan.FromMilliseconds(1000000000), + ReceiveBufferSize = 0x10000 + }); + + app.UseRouter(r => + { + // TODO all the verbs, but really MVC... + r.MapGet("/{*localpath}", ExecuteHandler); + r.MapPut("/{*localpath}", ExecuteHandler); + r.MapPost("/{*localpath}", ExecuteHandler); + r.MapDelete("/{*localpath}", ExecuteHandler); + r.MapVerb("HEAD", "/{*localpath}", ExecuteHandler); + }); + }) + .Build(); + } + + public async Task ExecuteHandler(HttpRequest request, Microsoft.AspNetCore.Http.HttpResponse response, RouteData data) + { + var ctx = request.HttpContext; + if (ctx.WebSockets.IsWebSocketRequest) + { + try + { + var endpoint = ctx.Request.Path.ToString(); + var url = ctx.Request.Path.ToString(); + + var queryString = new QueryParamCollection(request.Query); + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = queryString, + Endpoint = endpoint + }; + + if (connectingArgs.AllowConnection) + { + Logger.LogDebug("Web socket connection allowed"); + + var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + //socket.ConnectAsServerAsync().ConfigureAwait(false); + +// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) +// { +// OnReceive = ProcessWebSocketMessageReceived, +// Url = e.Url, +// QueryString = e.QueryString ?? new QueryParamCollection() +// }; +// +// connection.Closed += Connection_Closed; +// +// lock (_webSocketConnections) +// { +// _webSocketConnections.Add(connection); +// } +// +// WebSocketConnected(new WebSocketConnectEventArgs +// { +// Url = url, +// QueryString = queryString, +// WebSocket = socket, +// Endpoint = endpoint +// }); + await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + } + else + { + Logger.LogWarning("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + } + } + catch (Exception ex) + { + ctx.Response.StatusCode = 500; + } + } + + var req = new WebSocketSharpRequest(request, response, request.Path, Logger); + await ((HttpListenerHost)HttpServer).RequestHandler(req,request.Path.ToString(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() @@ -1067,7 +1182,7 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports()); - StartServer(); + //StartServer(); LibraryManager.AddParts(GetExports(), GetExports(), diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index bbf165d627..acbc60c39e 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -22,6 +22,16 @@ + + + + + + + + + + diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index fb42460f1b..1bd084259d 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -422,7 +422,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// Overridable method that can be used to implement a custom hnandler /// - 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 stopWatch = new Stopwatch(); stopWatch.Start(); diff --git a/Emby.Server.Implementations/SocketSharp/HttpFile.cs b/Emby.Server.Implementations/SocketSharp/HttpFile.cs new file mode 100644 index 0000000000..120ac50d9c --- /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 0000000000..f38ed848ee --- /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; + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + 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 0000000000..a8142aef68 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -0,0 +1,681 @@ +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; + +namespace Emby.Server.Implementations.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + internal static string GetParameter(string header, string attr) + { + int ap = header.IndexOf(attr, StringComparison.Ordinal); + if (ap == -1) + { + return null; + } + + ap += attr.Length; + if (ap >= header.Length) + { + return null; + } + + char ending = header[ap]; + if (ending != '"') + { + ending = ' '; + } + + int end = header.IndexOf(ending, ap + 1); + if (end == -1) + { + return ending == '"' ? null : header.Substring(ap); + } + + return header.Substring(ap + 1, end - ap - 1); + } + + private async Task LoadMultiPart(WebROCollection form) + { + string boundary = GetParameter(ContentType, "; 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; + input.Read(copy, 0, (int)e.Length); + + 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 GetFormData() + { + var form = new WebROCollection(); + files = new Dictionary(); + + if (IsContentType("multipart/form-data", true)) + { + await LoadMultiPart(form).ConfigureAwait(false); + } + else if (IsContentType("application/x-www-form-urlencoded", true)) + { + await LoadWwwForm(form).ConfigureAwait(false); + } + +#if NET_4_0 + if (validateRequestNewMode && !checked_form) { + // Setting this before calling the validator prevents + // possible endless recursion + checked_form = true; + ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); + } else +#endif + if (validate_form && !checked_form) + { + checked_form = true; + ValidateNameValueCollection("Form", form); + } + + return form; + } + + public string Accept => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString(); + + public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); + + protected bool validate_cookies { get; set; } + protected bool validate_query_string { get; set; } + protected bool validate_form { get; set; } + protected bool checked_cookies { get; set; } + protected bool checked_query_string { 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; + } + + public void ValidateInput() + { + validate_cookies = true; + validate_query_string = true; + validate_form = true; + } + + private bool IsContentType(string ct, bool starts_with) + { + if (ct == null || ContentType == null) + { + return false; + } + + if (starts_with) + { + return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); + } + + return string.Equals(ContentType, 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 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(); + string header; + while ((header = ReadHeaders()) != null) + { + if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) + { + elem.Name = GetContentDispositionAttribute(header, "name"); + elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); + } + else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) + { + elem.ContentType = header.Substring("Content-Type:".Length).Trim(); + 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(string l, string name) + { + int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); + if (idx < 0) + { + return null; + } + + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + { + return null; + } + + if (begin == end) + { + return string.Empty; + } + + return l.Substring(begin, end - begin); + } + + private string GetContentDispositionAttributeWithEncoding(string l, string name) + { + int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); + if (idx < 0) + { + return null; + } + + int begin = idx + name.Length + "=\"".Length; + int end = l.IndexOf('"', begin); + if (end < 0) + { + return null; + } + + if (begin == end) + { + return string.Empty; + } + + string temp = l.Substring(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 0000000000..80ef451dc7 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -0,0 +1,149 @@ +using System; +using System.Net.WebSockets; +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 + { + /// + /// The logger + /// + private readonly ILogger _logger; + + public event EventHandler Closed; + + /// + /// Gets or sets the web socket. + /// + /// The web socket. + private SocketHttpListener.WebSocket WebSocket { get; set; } + + private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); + private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); + private bool _disposed = false; + + public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) + { + if (socket == null) + { + throw new ArgumentNullException(nameof(socket)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _logger = logger; + WebSocket = socket; + + socket.OnMessage += OnSocketMessage; + socket.OnClose += OnSocketClose; + socket.OnError += OnSocketError; + } + + public Task ConnectAsServerAsync() + => WebSocket.ConnectAsServer(); + + public Task StartReceive() + { + return _taskCompletionSource.Task; + } + + private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) + { + _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); + + // Closed?.Invoke(this, EventArgs.Empty); + } + + private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) + { + _taskCompletionSource.TrySetResult(true); + + Closed?.Invoke(this, EventArgs.Empty); + } + + private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) + { + if (OnReceiveBytes != null) + { + OnReceiveBytes(e.RawData); + } + } + + /// + /// Gets or sets the state. + /// + /// The state. + public WebSocketState State => WebSocket.ReadyState; + + /// + /// Sends the async. + /// + /// The bytes. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) + { + return WebSocket.SendAsync(bytes); + } + + /// + /// Sends the asynchronous. + /// + /// The text. + /// if set to true [end of message]. + /// The cancellation token. + /// Task. + public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) + { + return WebSocket.SendAsync(text); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool dispose) + { + if (_disposed) + { + return; + } + + if (dispose) + { + WebSocket.OnMessage -= OnSocketMessage; + WebSocket.OnClose -= OnSocketClose; + WebSocket.OnError -= OnSocketError; + + _cancellationTokenSource.Cancel(); + + WebSocket.CloseAsync().GetAwaiter().GetResult(); + } + + _disposed = true; + } + + /// + /// Gets or sets the receive action. + /// + /// The receive action. + public Action OnReceiveBytes { get; set; } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs new file mode 100644 index 0000000000..ab7ddeca20 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; + using System.Net; +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.Extensions.Logging; + + namespace Emby.Server.Implementations.SocketSharp +{ + public class WebSocketSharpListener : IHttpListener + { + private HttpListener _listener; + + private readonly ILogger _logger; + + private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); + private CancellationToken _disposeCancellationToken; + + public WebSocketSharpListener( + ILogger logger) + { + _logger = logger; + + _disposeCancellationToken = _disposeCancellationTokenSource.Token; + } + + public Func ErrorHandler { get; set; } + public Func RequestHandler { get; set; } + + public Action WebSocketConnecting { get; set; } + + public Action WebSocketConnected { get; set; } + +// public void Start(IEnumerable urlPrefixes) +// { +// // TODO +// //if (_listener == null) +// //{ +// // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); +// //} +// +// //_listener.EnableDualMode = _enableDualMode; +// +// //if (_certificate != null) +// //{ +// // _listener.LoadCert(_certificate); +// //} +// +// //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); +// //_listener.Prefixes.AddRange(urlPrefixes); +// +// //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); +// +// //_listener.Start(); +// +// if (_listener == null) +// { +// _listener = new HttpListener(); +// } +// +// _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); +// +// //foreach (var urlPrefix in urlPrefixes) +// //{ +// // _listener.Prefixes.Add(urlPrefix); +// //} +// _listener.Prefixes.Add("http://localhost:8096/"); +// +// _listener.Start(); +// +// // TODO how to do this in netcore? +// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), +// null); +// } + + private static void LogRequest(ILogger logger, HttpListenerRequest request) + { + var url = request.Url.ToString(); + + logger.LogInformation( + "{0} {1}. UserAgent: {2}", + request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, + url, + request.UserAgent ?? string.Empty); + } +// +// private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) +// { +// var context = _listener.EndGetContext(asyncResult); +// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); +// IHttpRequest httpReq = null; +// var request = context.Request; +// +// try +// { +// if (request.IsWebSocketRequest) +// { +// LogRequest(_logger, request); +// +// return ProcessWebSocketRequest(context); +// } +// +// httpReq = GetRequest(context); +// } +// catch (Exception ex) +// { +// _logger.LogError(ex, "Error processing request"); +// +// httpReq = httpReq ?? GetRequest(context); +// return ErrorHandler(ex, httpReq, true, true); +// } +// +// var uri = request.Url; +// +// return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); +// } + + private async Task ProcessWebSocketRequest(HttpListenerContext ctx) + { + try + { + var endpoint = ctx.Request.RemoteEndPoint.ToString(); + var url = ctx.Request.RawUrl; + + var queryString = new QueryParamCollection(ctx.Request.QueryString); + + var connectingArgs = new WebSocketConnectingEventArgs + { + Url = url, + QueryString = queryString, + Endpoint = endpoint + }; + + WebSocketConnecting?.Invoke(connectingArgs); + + if (connectingArgs.AllowConnection) + { + _logger.LogDebug("Web socket connection allowed"); + + var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); + + if (WebSocketConnected != null) + { + SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); + await socket.ConnectAsServerAsync().ConfigureAwait(false); + + WebSocketConnected(new WebSocketConnectEventArgs + { + Url = url, + QueryString = queryString, + WebSocket = socket, + Endpoint = endpoint + }); + + await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + } + } + else + { + _logger.LogWarning("Web socket connection not allowed"); + ctx.Response.StatusCode = 401; + ctx.Response.Close(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "AcceptWebSocketAsync error"); + ctx.Response.StatusCode = 500; + ctx.Response.Close(); + } + } + + private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) + { + try + { + await socket.StartReceive().ConfigureAwait(false); + } + finally + { + TryClose(ctx, 200); + } + } + + private void TryClose(HttpListenerContext ctx, int statusCode) + { + try + { + ctx.Response.StatusCode = statusCode; + ctx.Response.Close(); + } + catch (ObjectDisposedException) + { + // TODO: Investigate and properly fix. + } + catch (Exception ex) + { + _logger.LogError(ex, "Error closing web socket response"); + } + } + + private IHttpRequest GetRequest(HttpRequest httpContext) + { + var urlSegments = httpContext.Path; + + var operationName = urlSegments; + + var req = new WebSocketSharpRequest(httpContext, httpContext.HttpContext.Response, operationName, _logger); + + return req; + } + + public void Start(IEnumerable urlPrefixes) + { + throw new NotImplementedException(); + } + + public Task Stop() + { + _disposeCancellationTokenSource.Cancel(); + _listener?.Close(); + + return Task.CompletedTask; + } + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool _disposed; + + /// + /// Releases the unmanaged resources and disposes of the managed resources used. + /// + /// Whether or not the managed resources should be disposed + 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 0000000000..facc544461 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -0,0 +1,539 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; +using SocketHttpListener.Net; +using IHttpFile = MediaBrowser.Model.Services.IHttpFile; +using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IResponse = MediaBrowser.Model.Services.IResponse; + +namespace Emby.Server.Implementations.SocketSharp +{ + public partial class WebSocketSharpRequest : IHttpRequest + { + private readonly HttpRequest request; + private readonly IHttpResponse response; + + public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) + { + this.OperationName = operationName; + this.request = httpContext; + this.response = new WebSocketSharpResponse(logger, response, this); + + // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); + } + + public HttpRequest HttpRequest => request; + + public object OriginalRequest => request; + + public IResponse Response => response; + + public IHttpResponse HttpResponse => response; + + public string OperationName { get; set; } + + public object Dto { get; set; } + + public string RawUrl => request.Path.ToUriComponent(); + + public string AbsoluteUri => request.Path.ToUriComponent().TrimEnd('/'); + + public string UserHostAddress => ""; + + 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 => + remoteIp ?? + (remoteIp = CheckBadChars(XForwardedFor) ?? + NormalizeIp(CheckBadChars(XRealIp) ?? + (string.IsNullOrEmpty(request.Host.Host) ? null : NormalizeIp(request.Host.Host)))); + + 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 string CheckBadChars(string name) + { + if (name == null || 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"); + } + + break; + } + + case 1: + { + if (c == '\n') + { + crlf = 2; + break; + } + + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + + case 2: + { + if (c == ' ' || c == '\t') + { + crlf = 0; + break; + } + + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + } + } + + if (crlf != 0) + { + throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); + } + + return name; + } + + internal static bool ContainsNonAsciiChars(string token) + { + for (int i = 0; i < token.Length; ++i) + { + if ((token[i] < 0x20) || (token[i] > 0x7e)) + { + return true; + } + } + + return false; + } + + private string NormalizeIp(string ip) + { + if (!string.IsNullOrWhiteSpace(ip)) + { + // Handle ipv4 mapped to ipv6 + const string srch = "::ffff:"; + var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); + if (index == 0) + { + ip = ip.Substring(srch.Length); + } + } + + return ip; + } + + public bool IsSecureConnection => request.IsHttps || XForwardedProtocol == "https"; + + public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); + + private Dictionary items; + public Dictionary Items => items ?? (items = new Dictionary()); + + 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); // TODO; + 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) + { + string format = httpReq.Query["format"]; + if (format == null) + { + const int formatMaxLength = 4; + string pi = httpReq.Path.ToString(); + if (pi == null || pi.Length <= formatMaxLength) + { + return null; + } + + if (pi[0] == '/') + { + pi = pi.Substring(1); + } + + format = LeftPart(pi, '/'); + if (format.Length > formatMaxLength) + { + return null; + } + } + + format = LeftPart(format, '.'); + if (format.ToLower().Contains("json")) + { + return "application/json"; + } + else if (format.ToLower().Contains("xml")) + { + return "application/xml"; + } + + return null; + } + + public static string LeftPart(string strVal, char needle) + { + if (strVal == null) + { + return null; + } + + var pos = strVal.IndexOf(needle.ToString(), StringComparison.Ordinal); + return pos == -1 ? strVal : strVal.Substring(0, pos); + } + + public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) + { + if (strVal == null) + { + return null; + } + + var pos = strVal.IndexOf(needle.ToString()); + 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 = request.Path.ToString().IndexOf("?", StringComparison.Ordinal); + if (pos != -1) + { + var path = request.Path.ToString().Substring(0, pos); + this.pathInfo = GetPathInfo( + path, + mode, + mode ?? string.Empty); + } + else + { + this.pathInfo = request.Path.ToString(); + } + + this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); + this.pathInfo = NormalizePathInfo(pathInfo, mode); + } + + 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; + } + + var path = sbPathInfo.ToString(); + return path.Length > 1 ? path.TrimEnd('/') : "/"; + } + + private Dictionary cookies; + public IDictionary Cookies + { + get + { + if (cookies == null) + { + cookies = new Dictionary(); + foreach (var cookie in this.request.Cookies) + { + var httpCookie = cookie; + cookies[httpCookie.Key] = new Cookie(httpCookie.Key, httpCookie.Value, "", ""); + } + } + + return cookies; + } + } + + public string UserAgent => request.Headers[HeaderNames.UserAgent]; + + public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + + private QueryParamCollection queryString; + public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + + public bool IsLocal => true; // TODO + + private string httpMethod; + public string HttpMethod => + httpMethod + ?? (httpMethod = request.Method); + + public string Verb => HttpMethod; + + public string ContentType => request.ContentType; + + private Encoding contentEncoding; + public Encoding ContentEncoding + { + get => contentEncoding ?? Encoding.GetEncoding(request.Headers[HeaderNames.ContentEncoding].ToString()); + set => contentEncoding = value; + } + + public Uri UrlReferrer => request.GetTypedHeaders().Referer; + + public static Encoding GetEncoding(string contentTypeHeader) + { + var param = GetParameter(contentTypeHeader, "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(); + } + + 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 string NormalizePathInfo(string pathInfo, string handlerPath) + { + if (handlerPath != null) + { + var trimmed = pathInfo.TrimStart('/'); + if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) + { + return trimmed.Substring(handlerPath.Length); + } + } + + return pathInfo; + } + } +} diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs new file mode 100644 index 0000000000..c68b959846 --- /dev/null +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Emby.Server.Implementations; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; +using IRequest = MediaBrowser.Model.Services.IRequest; + +namespace Emby.Server.Implementations.SocketSharp +{ + public class WebSocketSharpResponse : IHttpResponse + { + private readonly ILogger _logger; + + private readonly HttpResponse _response; + + public WebSocketSharpResponse(ILogger logger, HttpResponse response, IRequest request) + { + _logger = logger; + this._response = response; + Items = new Dictionary(); + Request = request; + } + + public IRequest Request { get; private set; } + + public Dictionary Items { get; private set; } + + public object OriginalResponse => _response; + + public int StatusCode + { + get => this._response.StatusCode; + set => this._response.StatusCode = value; + } + + public string StatusDescription { get; set; } + + public string ContentType + { + get => _response.ContentType; + set => _response.ContentType = value; + } + + public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); + + private static string AsHeaderValue(Cookie cookie) + { + DateTime defaultExpires = DateTime.MinValue; + + var path = cookie.Expires == defaultExpires + ? "/" + : cookie.Path ?? "/"; + + var sb = new StringBuilder(); + + sb.Append($"{cookie.Name}={cookie.Value};path={path}"); + + if (cookie.Expires != defaultExpires) + { + sb.Append($";expires={cookie.Expires:R}"); + } + + if (!string.IsNullOrEmpty(cookie.Domain)) + { + sb.Append($";domain={cookie.Domain}"); + } + + if (cookie.Secure) + { + sb.Append(";Secure"); + } + + if (cookie.HttpOnly) + { + sb.Append(";HttpOnly"); + } + + return sb.ToString(); + } + + public void AddHeader(string name, string value) + { + if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) + { + ContentType = value; + return; + } + + _response.Headers.Add(name, value); + } + + public string GetHeader(string name) + { + return _response.Headers[name]; + } + + public void Redirect(string url) + { + _response.Redirect(url); + } + + public Stream OutputStream => _response.Body; + + public void Close() + { + if (!this.IsClosed) + { + this.IsClosed = true; + + try + { + var response = this._response; + + var outputStream = response.Body; + + // This is needed with compression + outputStream.Flush(); + outputStream.Dispose(); + } + catch (SocketException) + { + } + catch (Exception ex) + { + _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); + } + } + } + + public bool IsClosed + { + get; + private set; + } + + public void SetContentLength(long contentLength) + { + // you can happily set the Content-Length header in Asp.Net + // but HttpListener will complain if you do - you have to set ContentLength64 on the response. + // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header + //_response.ContentLength64 = contentLength; + } + + public void SetCookie(Cookie cookie) + { + var cookieStr = AsHeaderValue(cookie); + _response.Headers.Add("Set-Cookie", cookieStr); + } + + public bool SendChunked { get; set; } + + public bool KeepAlive { get; set; } + + public void ClearCookies() + { + } + const int StreamCopyToBufferSize = 81920; + public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) + { + // TODO + // return _response.TransmitFile(path, offset, count, fileShareMode, 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/Startup.cs b/Emby.Server.Implementations/Startup.cs new file mode 100644 index 0000000000..164c7eeaa7 --- /dev/null +++ b/Emby.Server.Implementations/Startup.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using MediaBrowser.Api; +using MediaBrowser.Controller; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations +{ + public class Startup + { + public IConfiguration Configuration { get; } + + public Startup(IConfiguration configuration) => Configuration = configuration; + + // Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddRouting(); + } + + // Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + + } + } + +} diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 41ee73a565..bfc50468f7 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -20,6 +20,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -143,7 +144,7 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - + appHost.Host.Run(); // TODO: read input for a stop command try diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index f17fd7159d..2f1ea13d9b 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,6 +13,8 @@ + + diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 0e0ebf848b..09806ed9c2 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using System.Linq; using System.Net; using MediaBrowser.Model.Dto; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -23,6 +24,14 @@ namespace MediaBrowser.Model.Services } } + public QueryParamCollection(Microsoft.AspNetCore.Http.IHeaderDictionary headers) + { + foreach (var pair in headers) + { + Add(pair.Key, pair.Value); + } + } + // TODO remove this shit public QueryParamCollection(WebHeaderCollection webHeaderCollection) { @@ -47,6 +56,14 @@ namespace MediaBrowser.Model.Services } } + public QueryParamCollection(IQueryCollection queryCollection) + { + foreach (var pair in queryCollection) + { + Add(pair.Key, pair.Value); + } + } + private static StringComparison GetStringComparison() { return StringComparison.OrdinalIgnoreCase; From c3fa299acc1612966ee0d778c3e86f71c21a252a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 07:22:49 +0100 Subject: [PATCH 034/166] Remove hardcoded path and fix url bug in Windows --- Emby.Server.Implementations/ApplicationHost.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 5e8611d531..56089320ef 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -109,6 +109,7 @@ using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -621,8 +622,8 @@ namespace Emby.Server.Implementations Host = new WebHostBuilder() .UseKestrel() - .UseContentRoot("/Users/clausvium/RiderProjects/jellyfin/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin-web/src") - .UseStartup() + .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), @"jellyfin-web\src")) + //.UseStartup() // .ConfigureServices(async services => // { // services.AddSingleton(startUp); @@ -726,7 +727,7 @@ namespace Emby.Server.Implementations } var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req,request.Path.ToString(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); + await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() From f3e7bc0573e69df236ac8c565a354520dd094775 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 08:09:42 +0100 Subject: [PATCH 035/166] Replace some todos with http extensions and prepare some socket work --- .../ApplicationHost.cs | 110 ++++++++++-------- .../HttpServer/HttpListenerHost.cs | 7 ++ .../SocketSharp/WebSocketSharpListener.cs | 42 +++---- .../SocketSharp/WebSocketSharpRequest.cs | 13 ++- 4 files changed, 94 insertions(+), 78 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 56089320ef..18e752e53b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -110,10 +110,12 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; @@ -642,7 +644,12 @@ namespace Emby.Server.Implementations // await RunStartupTasks().ConfigureAwait(false); // }) .UseUrls("http://localhost:8096") - .ConfigureServices(s => s.AddRouting()) + .ConfigureServices(services => + { + services.AddRouting(); + services.AddHttpContextAccessor(); + services.TryAddSingleton(); + }) .Configure( app => { app.UseWebSockets(new WebSocketOptions { @@ -668,62 +675,63 @@ namespace Emby.Server.Implementations var ctx = request.HttpContext; if (ctx.WebSockets.IsWebSocketRequest) { - try - { - var endpoint = ctx.Request.Path.ToString(); - var url = ctx.Request.Path.ToString(); + await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(ctx).ConfigureAwait(false); +// try +// { +// var endpoint = ctx.Request.Path.ToString(); +// var url = ctx.Request.Path.ToString(); - var queryString = new QueryParamCollection(request.Query); + // var queryString = new QueryParamCollection(request.Query); - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryString, - Endpoint = endpoint - }; + // var connectingArgs = new WebSocketConnectingEventArgs + // { + // Url = url, + // QueryString = queryString, + // Endpoint = endpoint + // }; - if (connectingArgs.AllowConnection) - { - Logger.LogDebug("Web socket connection allowed"); + // if (connectingArgs.AllowConnection) + // { + // Logger.LogDebug("Web socket connection allowed"); - var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - //socket.ConnectAsServerAsync().ConfigureAwait(false); + // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + // //socket.ConnectAsServerAsync().ConfigureAwait(false); -// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) -// { -// OnReceive = ProcessWebSocketMessageReceived, -// Url = e.Url, -// QueryString = e.QueryString ?? new QueryParamCollection() -// }; -// -// connection.Closed += Connection_Closed; -// -// lock (_webSocketConnections) -// { -// _webSocketConnections.Add(connection); -// } -// -// WebSocketConnected(new WebSocketConnectEventArgs -// { -// Url = url, -// QueryString = queryString, -// WebSocket = socket, -// Endpoint = endpoint -// }); - await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - } - else - { - Logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; - } - } - catch (Exception ex) - { - ctx.Response.StatusCode = 500; - } + //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) + //// { + //// OnReceive = ProcessWebSocketMessageReceived, + //// Url = e.Url, + //// QueryString = e.QueryString ?? new QueryParamCollection() + //// }; + //// + //// connection.Closed += Connection_Closed; + //// + //// lock (_webSocketConnections) + //// { + //// _webSocketConnections.Add(connection); + //// } + //// + //// WebSocketConnected(new WebSocketConnectEventArgs + //// { + //// Url = url, + //// QueryString = queryString, + //// WebSocket = socket, + //// Endpoint = endpoint + //// }); + // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + // } + // else + // { + // Logger.LogWarning("Web socket connection not allowed"); + // ctx.Response.StatusCode = 401; + // } + // } + // catch (Exception ex) + // { + // ctx.Response.StatusCode = 500; + // } } var req = new WebSocketSharpRequest(request, response, request.Path, Logger); diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 1bd084259d..cfe0bbe974 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -11,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; @@ -73,6 +74,10 @@ namespace Emby.Server.Implementations.HttpServer Instance = this; ResponseFilters = Array.Empty>(); + _websocketlistener = new WebSocketSharpListener(_logger) + { + WebSocketConnected = OnWebSocketConnected + }; } public string GlobalResponse { get; set; } @@ -796,6 +801,8 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); + public WebSocketSharpListener _websocketlistener; + protected virtual void Dispose(bool disposing) { if (_disposed) return; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index ab7ddeca20..824c9a822d 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -8,6 +8,7 @@ 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; namespace Emby.Server.Implementations.SocketSharp @@ -120,14 +121,14 @@ using Microsoft.Extensions.Logging; // return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); // } - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) + public async Task ProcessWebSocketRequest(HttpContext ctx) { try { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; + var endpoint = ctx.Connection.RemoteIpAddress.ToString(); + var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.QueryString); + var queryString = new QueryParamCollection(ctx.Request.Query); var connectingArgs = new WebSocketConnectingEventArgs { @@ -142,40 +143,40 @@ using Microsoft.Extensions.Logging; { _logger.LogDebug("Web socket connection allowed"); - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); + var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); if (WebSocketConnected != null) { - SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); + //await socket.ConnectAsServerAsync().ConfigureAwait(false); - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = queryString, - WebSocket = socket, - Endpoint = endpoint - }); + //WebSocketConnected(new WebSocketConnectEventArgs + //{ + // Url = url, + // QueryString = queryString, + // WebSocket = socket, + // Endpoint = endpoint + //}); - await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } } else { _logger.LogWarning("Web socket connection not allowed"); ctx.Response.StatusCode = 401; - ctx.Response.Close(); + //ctx.Response.Close(); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); ctx.Response.StatusCode = 500; - ctx.Response.Close(); + //ctx.Response.Close(); } } - private async Task ReceiveWebSocketAsync(HttpListenerContext ctx, SharpWebSocket socket) + private async Task ReceiveWebSocketAsync(HttpContext ctx, SharpWebSocket socket) { try { @@ -187,12 +188,11 @@ using Microsoft.Extensions.Logging; } } - private void TryClose(HttpListenerContext ctx, int statusCode) + private void TryClose(HttpContext ctx, int statusCode) { try { ctx.Response.StatusCode = statusCode; - ctx.Response.Close(); } catch (ObjectDisposedException) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index facc544461..9c5b2b0834 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -7,6 +7,7 @@ using System.Text; using Emby.Server.Implementations.HttpServer; 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; @@ -44,11 +45,11 @@ namespace Emby.Server.Implementations.SocketSharp public object Dto { get; set; } - public string RawUrl => request.Path.ToUriComponent(); + public string RawUrl => request.Path.ToString(); - public string AbsoluteUri => request.Path.ToUriComponent().TrimEnd('/'); + public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - public string UserHostAddress => ""; + public string UserHostAddress => request.HttpContext.Connection.RemoteIpAddress.ToString(); public string XForwardedFor => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); @@ -66,7 +67,7 @@ namespace Emby.Server.Implementations.SocketSharp remoteIp ?? (remoteIp = CheckBadChars(XForwardedFor) ?? NormalizeIp(CheckBadChars(XRealIp) ?? - (string.IsNullOrEmpty(request.Host.Host) ? null : NormalizeIp(request.Host.Host)))); + (string.IsNullOrEmpty(request.HttpContext.Connection.RemoteIpAddress.ToString()) ? null : NormalizeIp(request.HttpContext.Connection.RemoteIpAddress.ToString())))); private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 }; @@ -199,7 +200,7 @@ namespace Emby.Server.Implementations.SocketSharp const string serverDefaultContentType = "application/json"; - var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); // TODO; + var acceptContentTypes = httpReq.Headers.GetCommaSeparatedValues(HeaderNames.Accept); string defaultContentType = null; if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { @@ -448,7 +449,7 @@ namespace Emby.Server.Implementations.SocketSharp private QueryParamCollection queryString; public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); - public bool IsLocal => true; // TODO + public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); private string httpMethod; public string HttpMethod => From 194da8416bb93b492b86bab6dd199eb5dcc06e55 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 10:23:58 +0100 Subject: [PATCH 036/166] Use middlewares instead of Routing --- .../ApplicationHost.cs | 147 +++++++++--------- .../SocketSharp/SharpWebSocket.cs | 14 +- 2 files changed, 79 insertions(+), 82 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 18e752e53b..ca1dceedfa 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -657,85 +657,92 @@ namespace Emby.Server.Implementations ReceiveBufferSize = 0x10000 }); - app.UseRouter(r => - { - // TODO all the verbs, but really MVC... - r.MapGet("/{*localpath}", ExecuteHandler); - r.MapPut("/{*localpath}", ExecuteHandler); - r.MapPost("/{*localpath}", ExecuteHandler); - r.MapDelete("/{*localpath}", ExecuteHandler); - r.MapVerb("HEAD", "/{*localpath}", ExecuteHandler); - }); + app.Use(ExecuteWebsocketHandlerAsync); + app.Use(ExecuteHttpHandlerAsync); }) .Build(); } - public async Task ExecuteHandler(HttpRequest request, Microsoft.AspNetCore.Http.HttpResponse response, RouteData data) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { - var ctx = request.HttpContext; - if (ctx.WebSockets.IsWebSocketRequest) + if (!context.WebSockets.IsWebSocketRequest) { - await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(ctx).ConfigureAwait(false); -// try -// { -// var endpoint = ctx.Request.Path.ToString(); -// var url = ctx.Request.Path.ToString(); - - // var queryString = new QueryParamCollection(request.Query); - - // var connectingArgs = new WebSocketConnectingEventArgs - // { - // Url = url, - // QueryString = queryString, - // Endpoint = endpoint - // }; - - // if (connectingArgs.AllowConnection) - // { - // Logger.LogDebug("Web socket connection allowed"); - - // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - // //socket.ConnectAsServerAsync().ConfigureAwait(false); - - //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) - //// { - //// OnReceive = ProcessWebSocketMessageReceived, - //// Url = e.Url, - //// QueryString = e.QueryString ?? new QueryParamCollection() - //// }; - //// - //// connection.Closed += Connection_Closed; - //// - //// lock (_webSocketConnections) - //// { - //// _webSocketConnections.Add(connection); - //// } - //// - //// WebSocketConnected(new WebSocketConnectEventArgs - //// { - //// Url = url, - //// QueryString = queryString, - //// WebSocket = socket, - //// Endpoint = endpoint - //// }); - // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - // } - // else - // { - // Logger.LogWarning("Web socket connection not allowed"); - // ctx.Response.StatusCode = 401; - // } - // } - // catch (Exception ex) - // { - // ctx.Response.StatusCode = 500; - // } + await next(); + return; } + await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(context).ConfigureAwait(false); + // try + // { + // var endpoint = ctx.Request.Path.ToString(); + // var url = ctx.Request.Path.ToString(); + + // var queryString = new QueryParamCollection(request.Query); + + // var connectingArgs = new WebSocketConnectingEventArgs + // { + // Url = url, + // QueryString = queryString, + // Endpoint = endpoint + // }; + + // if (connectingArgs.AllowConnection) + // { + // Logger.LogDebug("Web socket connection allowed"); + + // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; + + // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); + // //socket.ConnectAsServerAsync().ConfigureAwait(false); + + //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) + //// { + //// OnReceive = ProcessWebSocketMessageReceived, + //// Url = e.Url, + //// QueryString = e.QueryString ?? new QueryParamCollection() + //// }; + //// + //// connection.Closed += Connection_Closed; + //// + //// lock (_webSocketConnections) + //// { + //// _webSocketConnections.Add(connection); + //// } + //// + //// WebSocketConnected(new WebSocketConnectEventArgs + //// { + //// Url = url, + //// QueryString = queryString, + //// WebSocket = socket, + //// Endpoint = endpoint + //// }); + // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); + // } + // else + // { + // Logger.LogWarning("Web socket connection not allowed"); + // ctx.Response.StatusCode = 401; + // } + // } + // catch (Exception ex) + // { + // ctx.Response.StatusCode = 500; + // } + } + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + { + if (context.WebSockets.IsWebSocketRequest) + { + await next(); + return; + } + + var request = context.Request; + var response = context.Response; + var localPath = context.Request.Path.ToString().TrimStart('/'); + var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), data.Values["localpath"].ToString(), CancellationToken.None).ConfigureAwait(false); + await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 80ef451dc7..854d33f3a0 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -28,18 +28,8 @@ namespace Emby.Server.Implementations.SocketSharp public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - WebSocket = socket; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); socket.OnMessage += OnSocketMessage; socket.OnClose += OnSocketClose; From 38f52a139e8017ddaaef55e1eb4cc97f1d4d8c04 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 10:30:51 +0100 Subject: [PATCH 037/166] Add response compression middleware --- Emby.Server.Implementations/ApplicationHost.cs | 3 ++- Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ca1dceedfa..da9550b851 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -646,7 +646,7 @@ namespace Emby.Server.Implementations .UseUrls("http://localhost:8096") .ConfigureServices(services => { - services.AddRouting(); + services.AddResponseCompression(); services.AddHttpContextAccessor(); services.TryAddSingleton(); }) @@ -657,6 +657,7 @@ namespace Emby.Server.Implementations ReceiveBufferSize = 0x10000 }); + app.UseResponseCompression(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); }) diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index acbc60c39e..a4dac4a7f9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -29,6 +29,8 @@ + + From d6c6f3c10cff68b11df884be502c58a17da0d332 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 15:13:06 +0100 Subject: [PATCH 038/166] Still broken --- .../ApplicationHost.cs | 2 +- .../Net/WebSocketConnectEventArgs.cs | 1 + .../SocketSharp/SharpWebSocket.cs | 31 ++++++------- .../SocketSharp/WebSocketSharpListener.cs | 44 ++++++++++++++----- .../SocketSharp/WebSocketSharpListener.cs | 4 +- 5 files changed, 49 insertions(+), 33 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index da9550b851..65b6681c89 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -740,7 +740,7 @@ namespace Emby.Server.Implementations var request = context.Request; var response = context.Response; - var localPath = context.Request.Path.ToString().TrimStart('/'); + var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 3ab8e854a0..666f1f6011 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,4 +1,5 @@ using System; +using System.Net.WebSockets; using MediaBrowser.Model.Services; namespace Emby.Server.Implementations.Net diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 854d33f3a0..89004ba7fe 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -1,5 +1,6 @@ using System; using System.Net.WebSockets; +using System.Text; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; @@ -20,25 +21,18 @@ namespace Emby.Server.Implementations.SocketSharp /// Gets or sets the web socket. /// /// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } + private WebSocket WebSocket { get; set; } private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private bool _disposed = false; - public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) + public SharpWebSocket(WebSocket socket, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); - - socket.OnMessage += OnSocketMessage; - socket.OnClose += OnSocketClose; - socket.OnError += OnSocketError; } - public Task ConnectAsServerAsync() - => WebSocket.ConnectAsServer(); - public Task StartReceive() { return _taskCompletionSource.Task; @@ -58,7 +52,7 @@ namespace Emby.Server.Implementations.SocketSharp Closed?.Invoke(this, EventArgs.Empty); } - private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) + private void OnSocketMessage(SocketHttpListener.MessageEventArgs e) { if (OnReceiveBytes != null) { @@ -66,11 +60,15 @@ namespace Emby.Server.Implementations.SocketSharp } } + public Task ConnectAsServerAsync() + { + return Task.CompletedTask; + } /// /// Gets or sets the state. /// /// The state. - public WebSocketState State => WebSocket.ReadyState; + public WebSocketState State => WebSocket.State; /// /// Sends the async. @@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(bytes); + return WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); } /// @@ -93,7 +91,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(text); + return WebSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); } /// @@ -118,13 +116,10 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { - WebSocket.OnMessage -= OnSocketMessage; - WebSocket.OnClose -= OnSocketClose; - WebSocket.OnError -= OnSocketError; - _cancellationTokenSource.Cancel(); - WebSocket.CloseAsync().GetAwaiter().GetResult(); + // TODO + WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 824c9a822d..b64fc06833 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.HttpServer; @@ -144,22 +145,41 @@ using Microsoft.Extensions.Logging; _logger.LogDebug("Web socket connection allowed"); var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); + var socket = new SharpWebSocket(webSocketContext, _logger); + await socket.ConnectAsServerAsync().ConfigureAwait(false); - if (WebSocketConnected != null) + WebSocketConnected(new WebSocketConnectEventArgs { - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); - //await socket.ConnectAsServerAsync().ConfigureAwait(false); + Url = url, + QueryString = queryString, + WebSocket = socket, + Endpoint = endpoint + }); - //WebSocketConnected(new WebSocketConnectEventArgs - //{ - // Url = url, - // QueryString = queryString, - // WebSocket = socket, - // Endpoint = endpoint - //}); + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); + var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); + WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + socket.OnReceiveBytes(buffer.Array); + //while (!result.CloseStatus.HasValue) + //{ + // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); - } + // result = await webSocketContext.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + //} + //WebSocketConnected?.Invoke(new WebSocketConnectEventArgs + //{ + // Url = url, + // QueryString = queryString, + // WebSocket = webSocketContext, + // Endpoint = endpoint + //}); + await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); + //await socket.ConnectAsServerAsync().ConfigureAwait(false); + + + + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } else { diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs index c458fdb78f..4dc4103cb1 100644 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Net; using System.Threading; @@ -152,7 +152,7 @@ using Microsoft.Extensions.Logging; { Url = url, QueryString = queryString, - WebSocket = socket, + WebSocket = null, // socket, Endpoint = endpoint }); From 5a7cca9d1bdc5280626a6654cdbd8c0d45016af5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 19:48:18 +0100 Subject: [PATCH 039/166] Fix websockets and RawUrl --- .../SocketSharp/SharpWebSocket.cs | 2 +- .../SocketSharp/WebSocketSharpListener.cs | 13 +++++++++++-- .../SocketSharp/WebSocketSharpRequest.cs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 89004ba7fe..eab903db8e 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -119,7 +119,7 @@ namespace Emby.Server.Implementations.SocketSharp _cancellationTokenSource.Cancel(); // TODO - WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); + // WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index b64fc06833..05f159b4ee 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -160,6 +160,15 @@ using Microsoft.Extensions.Logging; var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); socket.OnReceiveBytes(buffer.Array); + + while (result.MessageType != WebSocketMessageType.Close) + { + result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + socket.OnReceiveBytes(buffer.Array); + } + await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + socket.Dispose(); + //while (!result.CloseStatus.HasValue) //{ // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); @@ -173,11 +182,11 @@ using Microsoft.Extensions.Logging; // WebSocket = webSocketContext, // Endpoint = endpoint //}); - await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + //await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); //await socket.ConnectAsServerAsync().ConfigureAwait(false); - + //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 9c5b2b0834..b07c4bfebb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -45,7 +45,7 @@ namespace Emby.Server.Implementations.SocketSharp public object Dto { get; set; } - public string RawUrl => request.Path.ToString(); + public string RawUrl => request.GetEncodedPathAndQuery(); public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); From a85488cd205532662c59ada5032dc81c0315543b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:13:48 +0100 Subject: [PATCH 040/166] Fix websockets array index out of bounds and some cleanup --- .../ApplicationHost.cs | 78 +------- .../HttpServer/HttpListenerHost.cs | 7 +- .../SocketSharp/WebSocketSharpListener.cs | 166 ++---------------- 3 files changed, 24 insertions(+), 227 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 65b6681c89..e9d8d8de7e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -624,25 +624,7 @@ namespace Emby.Server.Implementations Host = new WebHostBuilder() .UseKestrel() - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), @"jellyfin-web\src")) - //.UseStartup() -// .ConfigureServices(async services => -// { -// services.AddSingleton(startUp); -// RegisterResources(services); -// FindParts(); -// try -// { -// ImageProcessor.ImageEncoder = -// new NullImageEncoder(); //SkiaEncoder(_loggerFactory, appPaths, fileSystem, localizationManager); -// } -// catch (Exception ex) -// { -// Logger.LogInformation(ex, "Skia not available. Will fallback to NullIMageEncoder. {0}"); -// ImageProcessor.ImageEncoder = new NullImageEncoder(); -// } -// await RunStartupTasks().ConfigureAwait(false); -// }) + .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) .UseUrls("http://localhost:8096") .ConfigureServices(services => { @@ -672,63 +654,7 @@ namespace Emby.Server.Implementations return; } - await ((HttpListenerHost)HttpServer)._websocketlistener.ProcessWebSocketRequest(context).ConfigureAwait(false); - // try - // { - // var endpoint = ctx.Request.Path.ToString(); - // var url = ctx.Request.Path.ToString(); - - // var queryString = new QueryParamCollection(request.Query); - - // var connectingArgs = new WebSocketConnectingEventArgs - // { - // Url = url, - // QueryString = queryString, - // Endpoint = endpoint - // }; - - // if (connectingArgs.AllowConnection) - // { - // Logger.LogDebug("Web socket connection allowed"); - - // var webSocketContext = ctx.WebSockets.AcceptWebSocketAsync(null).Result; - - // //SharpWebSocket socket = new SharpWebSocket(webSocketContext, Logger); - // //socket.ConnectAsServerAsync().ConfigureAwait(false); - - //// var connection = new WebSocketConnection(webSocketContext, e.Endpoint, _jsonSerializer, _logger) - //// { - //// OnReceive = ProcessWebSocketMessageReceived, - //// Url = e.Url, - //// QueryString = e.QueryString ?? new QueryParamCollection() - //// }; - //// - //// connection.Closed += Connection_Closed; - //// - //// lock (_webSocketConnections) - //// { - //// _webSocketConnections.Add(connection); - //// } - //// - //// WebSocketConnected(new WebSocketConnectEventArgs - //// { - //// Url = url, - //// QueryString = queryString, - //// WebSocket = socket, - //// Endpoint = endpoint - //// }); - // await webSocketContext.ReceiveAsync(new ArraySegment(), CancellationToken.None).ConfigureAwait(false); - // } - // else - // { - // Logger.LogWarning("Web socket connection not allowed"); - // ctx.Response.StatusCode = 401; - // } - // } - // catch (Exception ex) - // { - // ctx.Response.StatusCode = 500; - // } + await ((HttpListenerHost)HttpServer).ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index cfe0bbe974..a3eaa2fb50 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -21,6 +21,7 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -767,6 +768,10 @@ namespace Emby.Server.Implementations.HttpServer return _jsonSerializer.DeserializeFromStreamAsync(stream, type); } + public Task ProcessWebSocketRequest(HttpContext context) + { + return _websocketlistener.ProcessWebSocketRequest(context); + } //TODO Add Jellyfin Route Path Normalizer private static string NormalizeEmbyRoutePath(string path) @@ -801,7 +806,7 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); - public WebSocketSharpListener _websocketlistener; + private readonly WebSocketSharpListener _websocketlistener; protected virtual void Dispose(bool disposing) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 05f159b4ee..6e5190efdb 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; - using System.Net; +using System.Linq; +using System.Net; using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; @@ -38,94 +39,18 @@ using Microsoft.Extensions.Logging; public Action WebSocketConnected { get; set; } -// public void Start(IEnumerable urlPrefixes) -// { -// // TODO -// //if (_listener == null) -// //{ -// // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); -// //} -// -// //_listener.EnableDualMode = _enableDualMode; -// -// //if (_certificate != null) -// //{ -// // _listener.LoadCert(_certificate); -// //} -// -// //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); -// //_listener.Prefixes.AddRange(urlPrefixes); -// -// //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); -// -// //_listener.Start(); -// -// if (_listener == null) -// { -// _listener = new HttpListener(); -// } -// -// _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); -// -// //foreach (var urlPrefix in urlPrefixes) -// //{ -// // _listener.Prefixes.Add(urlPrefix); -// //} -// _listener.Prefixes.Add("http://localhost:8096/"); -// -// _listener.Start(); -// -// // TODO how to do this in netcore? -// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), -// null); -// } - - private static void LogRequest(ILogger logger, HttpListenerRequest request) + private static void LogRequest(ILogger logger, HttpRequest request) { - var url = request.Url.ToString(); + var url = request.GetDisplayUrl(); - logger.LogInformation( - "{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, - url, - request.UserAgent ?? string.Empty); + logger.LogInformation("{0} {1}. UserAgent: {2}", "WS", url, request.Headers["User-Agent"].ToString()); } -// -// private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) -// { -// var context = _listener.EndGetContext(asyncResult); -// _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); -// IHttpRequest httpReq = null; -// var request = context.Request; -// -// try -// { -// if (request.IsWebSocketRequest) -// { -// LogRequest(_logger, request); -// -// return ProcessWebSocketRequest(context); -// } -// -// httpReq = GetRequest(context); -// } -// catch (Exception ex) -// { -// _logger.LogError(ex, "Error processing request"); -// -// httpReq = httpReq ?? GetRequest(context); -// return ErrorHandler(ex, httpReq, true, true); -// } -// -// var uri = request.Url; -// -// return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); -// } public async Task ProcessWebSocketRequest(HttpContext ctx) { try { + LogRequest(_logger, ctx.Request); var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); @@ -156,94 +81,35 @@ using Microsoft.Extensions.Logging; Endpoint = endpoint }); - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); - var buffer = WebSocket.CreateClientBuffer(1024 * 4, 1024 * 4); - WebSocketReceiveResult result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); - socket.OnReceiveBytes(buffer.Array); + var buffer = WebSocket.CreateClientBuffer(4096, 4096); + WebSocketReceiveResult result; + var message = new List(); - while (result.MessageType != WebSocketMessageType.Close) + do { result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); socket.OnReceiveBytes(buffer.Array); - } - await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); + message.AddRange(buffer.Array.Take(result.Count)); + } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); + + socket.OnReceiveBytes(message.ToArray()); + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, CancellationToken.None); socket.Dispose(); - - //while (!result.CloseStatus.HasValue) - //{ - // await webSocketContext.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None); - - // result = await webSocketContext.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); - //} - //WebSocketConnected?.Invoke(new WebSocketConnectEventArgs - //{ - // Url = url, - // QueryString = queryString, - // WebSocket = webSocketContext, - // Endpoint = endpoint - //}); - //await webSocketContext.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); - //SharpWebSocket socket = new SharpWebSocket(webSocketContext, _logger); - //await socket.ConnectAsServerAsync().ConfigureAwait(false); - - - - //await ReceiveWebSocketAsync(ctx, socket).ConfigureAwait(false); } else { _logger.LogWarning("Web socket connection not allowed"); ctx.Response.StatusCode = 401; - //ctx.Response.Close(); } } catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); ctx.Response.StatusCode = 500; - //ctx.Response.Close(); } } - private async Task ReceiveWebSocketAsync(HttpContext ctx, SharpWebSocket socket) - { - try - { - await socket.StartReceive().ConfigureAwait(false); - } - finally - { - TryClose(ctx, 200); - } - } - - private void TryClose(HttpContext ctx, int statusCode) - { - try - { - ctx.Response.StatusCode = statusCode; - } - catch (ObjectDisposedException) - { - // TODO: Investigate and properly fix. - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing web socket response"); - } - } - - private IHttpRequest GetRequest(HttpRequest httpContext) - { - var urlSegments = httpContext.Path; - - var operationName = urlSegments; - - var req = new WebSocketSharpRequest(httpContext, httpContext.HttpContext.Response, operationName, _logger); - - return req; - } - public void Start(IEnumerable urlPrefixes) { throw new NotImplementedException(); From 4e8de67acabbfe1aeb5fee6d1162abc760fbd540 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:22:40 +0100 Subject: [PATCH 041/166] Remove SocketSharp from Jellyfin.Server and some other cleanup --- .../ApplicationHost.cs | 45 +- .../HttpServer/HttpListenerHost.cs | 15 +- Jellyfin.Server/CoreAppHost.cs | 4 - Jellyfin.Server/Jellyfin.Server.csproj | 2 +- Jellyfin.Server/SocketSharp/HttpFile.cs | 18 - Jellyfin.Server/SocketSharp/HttpPostedFile.cs | 204 ------ Jellyfin.Server/SocketSharp/RequestMono.cs | 680 ------------------ Jellyfin.Server/SocketSharp/SharpWebSocket.cs | 149 ---- .../SocketSharp/WebSocketSharpListener.cs | 237 ------ .../SocketSharp/WebSocketSharpRequest.cs | 535 -------------- .../SocketSharp/WebSocketSharpResponse.cs | 215 ------ 11 files changed, 11 insertions(+), 2093 deletions(-) delete mode 100644 Jellyfin.Server/SocketSharp/HttpFile.cs delete mode 100644 Jellyfin.Server/SocketSharp/HttpPostedFile.cs delete mode 100644 Jellyfin.Server/SocketSharp/RequestMono.cs delete mode 100644 Jellyfin.Server/SocketSharp/SharpWebSocket.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs delete mode 100644 Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e9d8d8de7e..ad6b8e84a8 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -791,7 +791,8 @@ namespace Emby.Server.Implementations _configuration, NetworkManager, JsonSerializer, - XmlSerializer); + XmlSerializer, + CreateHttpListener()); HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); serviceCollection.AddSingleton(HttpServer); @@ -1125,8 +1126,6 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports()); - //StartServer(); - LibraryManager.AddParts(GetExports(), GetExports(), GetExports(), @@ -1239,45 +1238,7 @@ namespace Emby.Server.Implementations }); } - protected abstract IHttpListener CreateHttpListener(); - - /// - /// Starts the server. - /// - 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) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index a3eaa2fb50..12c23a8693 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -44,6 +44,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> _funcParseFn; public Action[] ResponseFilters { get; set; } @@ -61,7 +62,8 @@ namespace Emby.Server.Implementations.HttpServer IConfiguration configuration, INetworkManager networkManager, IJsonSerializer jsonSerializer, - IXmlSerializer xmlSerializer) + IXmlSerializer xmlSerializer, + IHttpListener socketListener) { _appHost = applicationHost; _logger = loggerFactory.CreateLogger("HttpServer"); @@ -70,15 +72,13 @@ namespace Emby.Server.Implementations.HttpServer _networkManager = networkManager; _jsonSerializer = jsonSerializer; _xmlSerializer = xmlSerializer; + _socketListener = socketListener; + _socketListener.WebSocketConnected = OnWebSocketConnected; _funcParseFn = t => s => JsvReader.GetParseFn(t)(s); Instance = this; ResponseFilters = Array.Empty>(); - _websocketlistener = new WebSocketSharpListener(_logger) - { - WebSocketConnected = OnWebSocketConnected - }; } public string GlobalResponse { get; set; } @@ -770,7 +770,8 @@ namespace Emby.Server.Implementations.HttpServer public Task ProcessWebSocketRequest(HttpContext context) { - return _websocketlistener.ProcessWebSocketRequest(context); + // TODO + return ((WebSocketSharpListener)_socketListener).ProcessWebSocketRequest(context); } //TODO Add Jellyfin Route Path Normalizer @@ -867,8 +868,6 @@ namespace Emby.Server.Implementations.HttpServer _listener.WebSocketConnected = OnWebSocketConnected; _listener.ErrorHandler = ErrorHandler; _listener.RequestHandler = RequestHandler; - - _listener.Start(UrlPrefixes); } } } diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 68fd2453e3..9e52247900 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Reflection; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; -using Jellyfin.Server.SocketSharp; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; @@ -45,8 +44,5 @@ namespace Jellyfin.Server } protected override void ShutdownInternal() => Program.Shutdown(); - - protected override IHttpListener CreateHttpListener() - => new WebSocketSharpListener(Logger); } } diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bd670df527..94a7e58cb5 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -1,4 +1,4 @@ - + jellyfin diff --git a/Jellyfin.Server/SocketSharp/HttpFile.cs b/Jellyfin.Server/SocketSharp/HttpFile.cs deleted file mode 100644 index 448b666b63..0000000000 --- a/Jellyfin.Server/SocketSharp/HttpFile.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.IO; -using MediaBrowser.Model.Services; - -namespace Jellyfin.Server.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/Jellyfin.Server/SocketSharp/HttpPostedFile.cs b/Jellyfin.Server/SocketSharp/HttpPostedFile.cs deleted file mode 100644 index f38ed848ee..0000000000 --- a/Jellyfin.Server/SocketSharp/HttpPostedFile.cs +++ /dev/null @@ -1,204 +0,0 @@ -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; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - 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/Jellyfin.Server/SocketSharp/RequestMono.cs b/Jellyfin.Server/SocketSharp/RequestMono.cs deleted file mode 100644 index f2a08c9ae2..0000000000 --- a/Jellyfin.Server/SocketSharp/RequestMono.cs +++ /dev/null @@ -1,680 +0,0 @@ -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; - -namespace Jellyfin.Server.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - internal static string GetParameter(string header, string attr) - { - int ap = header.IndexOf(attr, StringComparison.Ordinal); - if (ap == -1) - { - return null; - } - - ap += attr.Length; - if (ap >= header.Length) - { - return null; - } - - char ending = header[ap]; - if (ending != '"') - { - ending = ' '; - } - - int end = header.IndexOf(ending, ap + 1); - if (end == -1) - { - return ending == '"' ? null : header.Substring(ap); - } - - return header.Substring(ap + 1, end - ap - 1); - } - - private async Task LoadMultiPart(WebROCollection form) - { - string boundary = GetParameter(ContentType, "; 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; - input.Read(copy, 0, (int)e.Length); - - 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 GetFormData() - { - var form = new WebROCollection(); - files = new Dictionary(); - - if (IsContentType("multipart/form-data", true)) - { - await LoadMultiPart(form).ConfigureAwait(false); - } - else if (IsContentType("application/x-www-form-urlencoded", true)) - { - await LoadWwwForm(form).ConfigureAwait(false); - } - -#if NET_4_0 - if (validateRequestNewMode && !checked_form) { - // Setting this before calling the validator prevents - // possible endless recursion - checked_form = true; - ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); - } else -#endif - if (validate_form && !checked_form) - { - checked_form = true; - ValidateNameValueCollection("Form", form); - } - - return form; - } - - public string Accept => string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"]; - - public string Authorization => string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"]; - - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } - protected bool validate_form { get; set; } - protected bool checked_cookies { get; set; } - protected bool checked_query_string { 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; - } - - public void ValidateInput() - { - validate_cookies = true; - validate_query_string = true; - validate_form = true; - } - - private bool IsContentType(string ct, bool starts_with) - { - if (ct == null || ContentType == null) - { - return false; - } - - if (starts_with) - { - return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(ContentType, 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 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(); - string header; - while ((header = ReadHeaders()) != null) - { - if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase)) - { - elem.Name = GetContentDispositionAttribute(header, "name"); - elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename")); - } - else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase)) - { - elem.ContentType = header.Substring("Content-Type:".Length).Trim(); - 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(string l, string name) - { - int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - return l.Substring(begin, end - begin); - } - - private string GetContentDispositionAttributeWithEncoding(string l, string name) - { - int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal); - if (idx < 0) - { - return null; - } - - int begin = idx + name.Length + "=\"".Length; - int end = l.IndexOf('"', begin); - if (end < 0) - { - return null; - } - - if (begin == end) - { - return string.Empty; - } - - string temp = l.Substring(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/Jellyfin.Server/SocketSharp/SharpWebSocket.cs b/Jellyfin.Server/SocketSharp/SharpWebSocket.cs deleted file mode 100644 index 9b0951857f..0000000000 --- a/Jellyfin.Server/SocketSharp/SharpWebSocket.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations.Net; -using Microsoft.Extensions.Logging; - -namespace Jellyfin.Server.SocketSharp -{ - public class SharpWebSocket : IWebSocket - { - /// - /// The logger - /// - private readonly ILogger _logger; - - public event EventHandler Closed; - - /// - /// Gets or sets the web socket. - /// - /// The web socket. - private SocketHttpListener.WebSocket WebSocket { get; set; } - - private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); - private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed = false; - - public SharpWebSocket(SocketHttpListener.WebSocket socket, ILogger logger) - { - if (socket == null) - { - throw new ArgumentNullException(nameof(socket)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - _logger = logger; - WebSocket = socket; - - socket.OnMessage += OnSocketMessage; - socket.OnClose += OnSocketClose; - socket.OnError += OnSocketError; - } - - public Task ConnectAsServerAsync() - => WebSocket.ConnectAsServer(); - - public Task StartReceive() - { - return _taskCompletionSource.Task; - } - - private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) - { - _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); - - // Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) - { - _taskCompletionSource.TrySetResult(true); - - Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketMessage(object sender, SocketHttpListener.MessageEventArgs e) - { - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - /// - /// Gets or sets the state. - /// - /// The state. - public WebSocketState State => WebSocket.ReadyState; - - /// - /// Sends the async. - /// - /// The bytes. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) - { - return WebSocket.SendAsync(bytes); - } - - /// - /// Sends the asynchronous. - /// - /// The text. - /// if set to true [end of message]. - /// The cancellation token. - /// Task. - public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) - { - return WebSocket.SendAsync(text); - } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Releases unmanaged and - optionally - managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected virtual void Dispose(bool dispose) - { - if (_disposed) - { - return; - } - - if (dispose) - { - WebSocket.OnMessage -= OnSocketMessage; - WebSocket.OnClose -= OnSocketClose; - WebSocket.OnError -= OnSocketError; - - _cancellationTokenSource.Cancel(); - - WebSocket.CloseAsync().GetAwaiter().GetResult(); - } - - _disposed = true; - } - - /// - /// Gets or sets the receive action. - /// - /// The receive action. - public Action OnReceiveBytes { get; set; } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs deleted file mode 100644 index 4dc4103cb1..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpListener.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; - using System.Net; -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.Extensions.Logging; - - namespace Jellyfin.Server.SocketSharp -{ - public class WebSocketSharpListener : IHttpListener - { - private HttpListener _listener; - - private readonly ILogger _logger; - - private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); - private CancellationToken _disposeCancellationToken; - - public WebSocketSharpListener( - ILogger logger) - { - _logger = logger; - - _disposeCancellationToken = _disposeCancellationTokenSource.Token; - } - - public Func ErrorHandler { get; set; } - public Func RequestHandler { get; set; } - - public Action WebSocketConnecting { get; set; } - - public Action WebSocketConnected { get; set; } - - public void Start(IEnumerable urlPrefixes) - { - // TODO - //if (_listener == null) - //{ - // _listener = new HttpListener(_logger, _cryptoProvider, _socketFactory, _streamHelper, _fileSystem, _environment); - //} - - //_listener.EnableDualMode = _enableDualMode; - - //if (_certificate != null) - //{ - // _listener.LoadCert(_certificate); - //} - - //_logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - //_listener.Prefixes.AddRange(urlPrefixes); - - //_listener.OnContext = async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false); - - //_listener.Start(); - - if (_listener == null) - { - _listener = new HttpListener(); - } - - _logger.LogInformation("Adding HttpListener prefixes {Prefixes}", urlPrefixes); - - //foreach (var urlPrefix in urlPrefixes) - //{ - // _listener.Prefixes.Add(urlPrefix); - //} - _listener.Prefixes.Add("http://localhost:8096/"); - - _listener.Start(); - - // TODO how to do this in netcore? - _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), - null); - } - - private static void LogRequest(ILogger logger, HttpListenerRequest request) - { - var url = request.Url.ToString(); - - logger.LogInformation( - "{0} {1}. UserAgent: {2}", - request.IsWebSocketRequest ? "WS" : "HTTP " + request.HttpMethod, - url, - request.UserAgent ?? string.Empty); - } - - private Task InitTask(IAsyncResult asyncResult, CancellationToken cancellationToken) - { - var context = _listener.EndGetContext(asyncResult); - _listener.BeginGetContext(async c => await InitTask(c, _disposeCancellationToken).ConfigureAwait(false), null); - IHttpRequest httpReq = null; - var request = context.Request; - - try - { - if (request.IsWebSocketRequest) - { - LogRequest(_logger, request); - - return ProcessWebSocketRequest(context); - } - - httpReq = GetRequest(context); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing request"); - - httpReq = httpReq ?? GetRequest(context); - return ErrorHandler(ex, httpReq, true, true); - } - - var uri = request.Url; - - return RequestHandler(httpReq, uri.OriginalString, uri.Host, uri.LocalPath, cancellationToken); - } - - private async Task ProcessWebSocketRequest(HttpListenerContext ctx) - { - try - { - var endpoint = ctx.Request.RemoteEndPoint.ToString(); - var url = ctx.Request.RawUrl; - - var queryString = new QueryParamCollection(ctx.Request.QueryString); - - var connectingArgs = new WebSocketConnectingEventArgs - { - Url = url, - QueryString = queryString, - Endpoint = endpoint - }; - - WebSocketConnecting?.Invoke(connectingArgs); - - if (connectingArgs.AllowConnection) - { - _logger.LogDebug("Web socket connection allowed"); - - var webSocketContext = await ctx.AcceptWebSocketAsync(null).ConfigureAwait(false); - - if (WebSocketConnected != null) - { - SharpWebSocket socket = null; //new SharpWebSocket(webSocketContext.WebSocket, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); - - WebSocketConnected(new WebSocketConnectEventArgs - { - Url = url, - QueryString = queryString, - WebSocket = null, // socket, - Endpoint = endpoint - }); - - await socket.StartReceive().ConfigureAwait(false); - } - } - else - { - _logger.LogWarning("Web socket connection not allowed"); - TryClose(ctx, 401); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "AcceptWebSocketAsync error"); - TryClose(ctx, 500); - } - } - - private void TryClose(HttpListenerContext ctx, int statusCode) - { - try - { - ctx.Response.StatusCode = statusCode; - ctx.Response.Close(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error closing web socket response"); - } - } - - private IHttpRequest GetRequest(HttpListenerContext httpContext) - { - var urlSegments = httpContext.Request.Url.Segments; - - var operationName = urlSegments[urlSegments.Length - 1]; - - var req = new WebSocketSharpRequest(httpContext, operationName, _logger); - - return req; - } - - public Task Stop() - { - _disposeCancellationTokenSource.Cancel(); - _listener?.Close(); - - return Task.CompletedTask; - } - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool _disposed; - - /// - /// Releases the unmanaged resources and disposes of the managed resources used. - /// - /// Whether or not the managed resources should be disposed - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - Stop().GetAwaiter().GetResult(); - } - - _disposed = true; - } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs deleted file mode 100644 index 6a00e283c6..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpRequest.cs +++ /dev/null @@ -1,535 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Net; -using System.Text; -using Emby.Server.Implementations.HttpServer; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using SocketHttpListener.Net; -using IHttpFile = MediaBrowser.Model.Services.IHttpFile; -using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IResponse = MediaBrowser.Model.Services.IResponse; - -namespace Jellyfin.Server.SocketSharp -{ - public partial class WebSocketSharpRequest : IHttpRequest - { - private readonly HttpListenerRequest request; - private readonly IHttpResponse response; - - public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger) - { - this.OperationName = operationName; - this.request = httpContext.Request; - this.response = new WebSocketSharpResponse(logger, httpContext.Response, this); - - // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); - } - - public HttpListenerRequest HttpRequest => request; - - public object OriginalRequest => request; - - public IResponse Response => response; - - public IHttpResponse HttpResponse => response; - - public string OperationName { get; set; } - - public object Dto { get; set; } - - public string RawUrl => request.RawUrl; - - public string AbsoluteUri => request.Url.AbsoluteUri.TrimEnd('/'); - - public string UserHostAddress => request.UserHostAddress; - - public string XForwardedFor - => string.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"]; - - public int? XForwardedPort - => string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"], CultureInfo.InvariantCulture); - - public string XForwardedProtocol => string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"]; - - public string XRealIp => string.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"]; - - private string remoteIp; - public string RemoteIp => - remoteIp ?? - (remoteIp = CheckBadChars(XForwardedFor) ?? - NormalizeIp(CheckBadChars(XRealIp) ?? - (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.Address.ToString()) : null))); - - 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 string CheckBadChars(string name) - { - if (name == null || 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"); - } - - break; - } - - case 1: - { - if (c == '\n') - { - crlf = 2; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - - case 2: - { - if (c == ' ' || c == '\t') - { - crlf = 0; - break; - } - - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - } - } - - if (crlf != 0) - { - throw new ArgumentException("net_WebHeaderInvalidCRLFChars"); - } - - return name; - } - - internal static bool ContainsNonAsciiChars(string token) - { - for (int i = 0; i < token.Length; ++i) - { - if ((token[i] < 0x20) || (token[i] > 0x7e)) - { - return true; - } - } - - return false; - } - - private string NormalizeIp(string ip) - { - if (!string.IsNullOrWhiteSpace(ip)) - { - // Handle ipv4 mapped to ipv6 - const string srch = "::ffff:"; - var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase); - if (index == 0) - { - ip = ip.Substring(srch.Length); - } - } - - return ip; - } - - public bool IsSecureConnection => request.IsSecureConnection || XForwardedProtocol == "https"; - - public string[] AcceptTypes => request.AcceptTypes; - - private Dictionary items; - public Dictionary Items => items ?? (items = new Dictionary()); - - private string responseContentType; - public string ResponseContentType - { - get => - responseContentType - ?? (responseContentType = GetResponseContentType(this)); - set => this.responseContentType = value; - } - - public const string FormUrlEncoded = "application/x-www-form-urlencoded"; - public const string MultiPartFormData = "multipart/form-data"; - public static string GetResponseContentType(IRequest httpReq) - { - var specifiedContentType = GetQueryStringContentType(httpReq); - if (!string.IsNullOrEmpty(specifiedContentType)) - { - return specifiedContentType; - } - - const string serverDefaultContentType = "application/json"; - - var acceptContentTypes = httpReq.AcceptTypes; - 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(IRequest 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(IRequest request, string contentType) - { - return request.ContentType.StartsWith(contentType, StringComparison.OrdinalIgnoreCase); - } - - private static string GetQueryStringContentType(IRequest httpReq) - { - ReadOnlySpan format = httpReq.QueryString["format"]; - if (format == null) - { - const int formatMaxLength = 4; - ReadOnlySpan pi = httpReq.PathInfo; - 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", StringComparison.OrdinalIgnoreCase)) - { - return "application/json"; - } - else if (format.Contains("xml", StringComparison.OrdinalIgnoreCase)) - { - return "application/xml"; - } - - return null; - } - - public static string LeftPart(string strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle, StringComparison.Ordinal); - return pos == -1 ? strVal : strVal.Substring(0, pos); - } - - public static ReadOnlySpan LeftPart(ReadOnlySpan 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 = request.RawUrl.IndexOf('?', StringComparison.Ordinal); - if (pos != -1) - { - var path = request.RawUrl.Substring(0, pos); - this.pathInfo = GetPathInfo( - path, - mode, - mode ?? string.Empty); - } - else - { - this.pathInfo = request.RawUrl; - } - - this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); - this.pathInfo = NormalizePathInfo(pathInfo, mode); - } - - 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; - } - - var path = sbPathInfo.ToString(); - return path.Length > 1 ? path.TrimEnd('/') : "/"; - } - - private Dictionary cookies; - public IDictionary Cookies - { - get - { - if (cookies == null) - { - cookies = new Dictionary(); - foreach (var cookie in this.request.Cookies) - { - var httpCookie = (System.Net.Cookie)cookie; - cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain); - } - } - - return cookies; - } - } - - public string UserAgent => request.UserAgent; - - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); - - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); - - public bool IsLocal => request.IsLocal; - - private string httpMethod; - public string HttpMethod => - httpMethod - ?? (httpMethod = request.HttpMethod); - - public string Verb => HttpMethod; - - public string ContentType => request.ContentType; - - private Encoding contentEncoding; - public Encoding ContentEncoding - { - get => contentEncoding ?? request.ContentEncoding; - set => contentEncoding = value; - } - - public Uri UrlReferrer => request.UrlReferrer; - - public static Encoding GetEncoding(string contentTypeHeader) - { - var param = GetParameter(contentTypeHeader, "charset="); - if (param == null) - { - return null; - } - - try - { - return Encoding.GetEncoding(param); - } - catch (ArgumentException) - { - return null; - } - } - - public Stream InputStream => request.InputStream; - - public long ContentLength => request.ContentLength64; - - private IHttpFile[] httpFiles; - public IHttpFile[] Files - { - get - { - if (httpFiles == null) - { - if (files == null) - { - return httpFiles = Array.Empty(); - } - - 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 string NormalizePathInfo(string pathInfo, string handlerPath) - { - if (handlerPath != null) - { - var trimmed = pathInfo.TrimStart('/'); - if (trimmed.StartsWith(handlerPath, StringComparison.OrdinalIgnoreCase)) - { - return trimmed.Substring(handlerPath.Length); - } - } - - return pathInfo; - } - } -} diff --git a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs b/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs deleted file mode 100644 index 552ae1754d..0000000000 --- a/Jellyfin.Server/SocketSharp/WebSocketSharpResponse.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Emby.Server.Implementations; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; -using IRequest = MediaBrowser.Model.Services.IRequest; - -namespace Jellyfin.Server.SocketSharp -{ - public class WebSocketSharpResponse : IHttpResponse - { - private readonly ILogger _logger; - - private readonly HttpListenerResponse _response; - - public WebSocketSharpResponse(ILogger logger, HttpListenerResponse response, IRequest request) - { - _logger = logger; - this._response = response; - Items = new Dictionary(); - Request = request; - } - - public IRequest Request { get; private set; } - - public Dictionary Items { get; private set; } - - public object OriginalResponse => _response; - - public int StatusCode - { - get => this._response.StatusCode; - set => this._response.StatusCode = value; - } - - public string StatusDescription - { - get => this._response.StatusDescription; - set => this._response.StatusDescription = value; - } - - public string ContentType - { - get => _response.ContentType; - set => _response.ContentType = value; - } - - public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); - - private static string AsHeaderValue(Cookie cookie) - { - DateTime defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } - - public void AddHeader(string name, string value) - { - if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) - { - ContentType = value; - return; - } - - _response.AddHeader(name, value); - } - - public string GetHeader(string name) - { - return _response.Headers[name]; - } - - public void Redirect(string url) - { - _response.Redirect(url); - } - - public Stream OutputStream => _response.OutputStream; - - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.OutputStream; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - - response.Close(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - - public bool IsClosed - { - get; - private set; - } - - public void SetContentLength(long contentLength) - { - // you can happily set the Content-Length header in Asp.Net - // but HttpListener will complain if you do - you have to set ContentLength64 on the response. - // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - //_response.ContentLength64 = contentLength; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); - } - - public bool SendChunked - { - get => _response.SendChunked; - set => _response.SendChunked = value; - } - - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } - const int StreamCopyToBufferSize = 81920; - public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) - { - // TODO - // return _response.TransmitFile(path, offset, count, fileShareMode, 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); - } - } - } - } -} From e88f079da64a978d560f739e9bf17cd0a93d122a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:40:05 +0100 Subject: [PATCH 042/166] Remove websocket options and configure kestrel listen ports --- .../ApplicationHost.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ad6b8e84a8..7be39d6744 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -111,13 +111,11 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; -using HttpResponse = MediaBrowser.Model.Net.HttpResponse; using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations @@ -623,21 +621,24 @@ namespace Emby.Server.Implementations FindParts(); Host = new WebHostBuilder() - .UseKestrel() + .UseKestrel(options => + { + options.Listen(IPAddress.Any, HttpPort); + options.Listen(IPAddress.Loopback, HttpPort); + // TODO certs + options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + }) .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) - .UseUrls("http://localhost:8096") .ConfigureServices(services => { services.AddResponseCompression(); services.AddHttpContextAccessor(); services.TryAddSingleton(); }) - .Configure( app => + .Configure(app => { - app.UseWebSockets(new WebSocketOptions { - KeepAliveInterval = TimeSpan.FromMilliseconds(1000000000), - ReceiveBufferSize = 0x10000 - }); + app.UseWebSockets(); app.UseResponseCompression(); app.Use(ExecuteWebsocketHandlerAsync); From 4e229ad86b3a3b6024ca0c29c17e4aac5386a210 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 20:50:10 +0100 Subject: [PATCH 043/166] Fix PathInfo --- .../SocketSharp/WebSocketSharpRequest.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index b07c4bfebb..146862e02b 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -341,10 +341,10 @@ namespace Emby.Server.Implementations.SocketSharp { var mode = HandlerFactoryPath; - var pos = request.Path.ToString().IndexOf("?", StringComparison.Ordinal); + var pos = RawUrl.IndexOf("?", StringComparison.Ordinal); if (pos != -1) { - var path = request.Path.ToString().Substring(0, pos); + var path = RawUrl.Substring(0, pos); this.pathInfo = GetPathInfo( path, mode, @@ -352,10 +352,10 @@ namespace Emby.Server.Implementations.SocketSharp } else { - this.pathInfo = request.Path.ToString(); + this.pathInfo = RawUrl; } - this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo); + this.pathInfo = WebUtility.UrlDecode(pathInfo); this.pathInfo = NormalizePathInfo(pathInfo, mode); } From f1c93ae618f293eae4cc384fadfd4440c4e0cf2d Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:08:30 +0100 Subject: [PATCH 044/166] Remove SetContentLength and company --- .../HttpClientManager/HttpClientManager.cs | 1 - .../HttpServer/FileWriter.cs | 3 --- .../HttpServer/HttpListenerHost.cs | 6 +----- .../HttpServer/HttpResultFactory.cs | 13 ++++--------- .../HttpServer/RangeRequestWriter.cs | 3 +-- .../HttpServer/ResponseFilter.cs | 1 - .../HttpServer/StreamWriter.cs | 10 +--------- Emby.Server.Implementations/Services/HttpResult.cs | 7 +------ .../Services/ResponseHelper.cs | 7 ------- .../SocketSharp/WebSocketSharpResponse.cs | 8 -------- MediaBrowser.Model/Services/IRequest.cs | 2 -- 11 files changed, 8 insertions(+), 53 deletions(-) diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index 834a494f81..ef82d1d9ee 100644 --- a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs +++ b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs @@ -327,7 +327,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) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 835c6f52ef..1375089e39 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -63,8 +63,6 @@ namespace Emby.Server.Implementations.HttpServer if (string.IsNullOrWhiteSpace(rangeHeader)) { - // TODO - //Headers["Content-Length"] = TotalContentLength.ToString(UsCulture); StatusCode = HttpStatusCode.OK; } else @@ -99,7 +97,6 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - // TODO Headers["Content-Length"] = lengthString; var rangeString = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); Headers["Content-Range"] = rangeString; diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 12c23a8693..6b69fa0f4e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -649,11 +649,6 @@ namespace Emby.Server.Implementations.HttpServer private static Task Write(IResponse response, string text) { var bOutput = Encoding.UTF8.GetBytes(text); - response.SetContentLength(bOutput.Length); - // TODO - response.Headers.Remove("Content-Length"); // DO NOT SET THIS, IT'S DONE AUTOMATICALLY BECAUSE MS ARE NOT STUPID - response.SendChunked = true; - return response.OutputStream.WriteAsync(bOutput, 0, bOutput.Length); } @@ -672,6 +667,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); diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 5e9d2b4c36..09cdbc3c26 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.HttpServer content = Array.Empty(); } - result = new StreamWriter(content, contentType, contentLength); + result = new StreamWriter(content, contentType); } else { @@ -175,7 +175,7 @@ namespace Emby.Server.Implementations.HttpServer bytes = Array.Empty(); } - result = new StreamWriter(bytes, contentType, contentLength); + result = new StreamWriter(bytes, contentType); } else { @@ -334,13 +334,13 @@ namespace Emby.Server.Implementations.HttpServer if (isHeadRequest) { - var result = new StreamWriter(Array.Empty(), contentType, contentLength); + var result = new StreamWriter(Array.Empty(), contentType); AddResponseHeaders(result, responseHeaders); return result; } else { - var result = new StreamWriter(content, contentType, contentLength); + var result = new StreamWriter(content, contentType); AddResponseHeaders(result, responseHeaders); return result; } @@ -590,11 +590,6 @@ namespace Emby.Server.Implementations.HttpServer } else { - if (totalContentLength.HasValue) - { - // TODO responseHeaders["Content-Length"] = totalContentLength.Value.ToString(UsCulture); - } - if (isHeadRequest) { using (stream) diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 9c8ab8d91c..8904e11d30 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,8 +96,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - // TODO Headers["Content-Length"] = RangeLength.ToString(UsCulture); - Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", RangeStart, RangeEnd, TotalContentLength); + Headers["Content-Range"] = $"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 dbce3250d4..ae6a6576e0 100644 --- a/Emby.Server.Implementations/HttpServer/ResponseFilter.cs +++ b/Emby.Server.Implementations/HttpServer/ResponseFilter.cs @@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.HttpServer if (length > 0) { - res.SetContentLength(length); res.SendChunked = false; } } diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 750b0f7958..90354385dc 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -53,11 +53,6 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; Headers["Content-Type"] = contentType; - - if (source.CanSeek) - { - // TODO Headers["Content-Length"] = source.Length.ToString(UsCulture); - } } /// @@ -65,8 +60,7 @@ namespace Emby.Server.Implementations.HttpServer /// /// The source. /// Type of the content. - /// The logger. - public StreamWriter(byte[] source, string contentType, int contentLength) + public StreamWriter(byte[] source, string contentType) { if (string.IsNullOrEmpty(contentType)) { @@ -76,8 +70,6 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; Headers["Content-Type"] = contentType; - - // TODO Headers["Content-Length"] = contentLength.ToString(UsCulture); } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/Services/HttpResult.cs b/Emby.Server.Implementations/Services/HttpResult.cs index 296da2f7a0..b6758486ce 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 dc99753477..0301ff335f 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/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index c68b959846..f9ecb52a55 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -143,14 +143,6 @@ namespace Emby.Server.Implementations.SocketSharp private set; } - public void SetContentLength(long contentLength) - { - // you can happily set the Content-Length header in Asp.Net - // but HttpListener will complain if you do - you have to set ContentLength64 on the response. - // workaround: HttpListener throws "The parameter is incorrect" exceptions when we try to set the Content-Length header - //_response.ContentLength64 = contentLength; - } - public void SetCookie(Cookie cookie) { var cookieStr = AsHeaderValue(cookie); diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 909cebce3d..b4ad24fec8 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -145,8 +145,6 @@ namespace MediaBrowser.Model.Services /// bool IsClosed { get; } - void SetContentLength(long contentLength); - //Add Metadata to Response Dictionary Items { get; } From e342b7bc71f16286da699bcce299b76df63360dc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:27:02 +0100 Subject: [PATCH 045/166] Extend the IHttpServer interface to avoid the typecasting --- .../ApplicationHost.cs | 4 ++-- .../HttpServer/IHttpListener.cs | 14 ------------ MediaBrowser.Controller/Net/IHttpServer.cs | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7be39d6744..a1ea179040 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -655,7 +655,7 @@ namespace Emby.Server.Implementations return; } - await ((HttpListenerHost)HttpServer).ProcessWebSocketRequest(context).ConfigureAwait(false); + await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { @@ -670,7 +670,7 @@ namespace Emby.Server.Implementations var localPath = context.Request.Path.ToString(); var req = new WebSocketSharpRequest(request, response, request.Path, Logger); - await ((HttpListenerHost)HttpServer).RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); + await HttpServer.RequestHandler(req, request.GetDisplayUrl(), request.Host.ToString(), localPath, CancellationToken.None).ConfigureAwait(false); } protected virtual IHttpClient CreateHttpClient() diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 8350913610..1ef65d9d73 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -1,9 +1,7 @@ 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; namespace Emby.Server.Implementations.HttpServer @@ -28,18 +26,6 @@ namespace Emby.Server.Implementations.HttpServer /// The web socket handler. Action WebSocketConnected { get; set; } - /// - /// Gets or sets the web socket connecting. - /// - /// The web socket connecting. - Action WebSocketConnecting { get; set; } - - /// - /// Starts this instance. - /// - /// The URL prefixes. - void Start(IEnumerable urlPrefixes); - /// /// Stops this instance. /// diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index f413030076..cede9a98fb 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; using MediaBrowser.Model.Events; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,5 +38,24 @@ namespace MediaBrowser.Controller.Net /// If set, all requests will respond with this message /// string GlobalResponse { get; set; } + + /// + /// Sends the http context to the socket listener + /// + /// + /// + Task ProcessWebSocketRequest(HttpContext ctx); + + /// + /// The HTTP request handler + /// + /// + /// + /// + /// + /// + /// + Task RequestHandler(IHttpRequest httpReq, string urlString, string host, string localPath, + CancellationToken cancellationToken); } } From 148db8b81ac4bb52262e071e6920b1449c8611fe Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 21:51:02 +0100 Subject: [PATCH 046/166] Remove unused SharpSocket stuff --- .../SocketSharp/SharpWebSocket.cs | 44 +++---------------- .../SocketSharp/WebSocketSharpListener.cs | 1 - 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index eab903db8e..66c5cc334b 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -21,7 +21,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Gets or sets the web socket. /// /// The web socket. - private WebSocket WebSocket { get; set; } + private readonly WebSocket _webSocket; private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); @@ -30,45 +30,14 @@ namespace Emby.Server.Implementations.SocketSharp public SharpWebSocket(WebSocket socket, ILogger logger) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - WebSocket = socket ?? throw new ArgumentNullException(nameof(socket)); + _webSocket = socket ?? throw new ArgumentNullException(nameof(socket)); } - public Task StartReceive() - { - return _taskCompletionSource.Task; - } - - private void OnSocketError(object sender, SocketHttpListener.ErrorEventArgs e) - { - _logger.LogError("Error in SharpWebSocket: {Message}", e.Message ?? string.Empty); - - // Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketClose(object sender, SocketHttpListener.CloseEventArgs e) - { - _taskCompletionSource.TrySetResult(true); - - Closed?.Invoke(this, EventArgs.Empty); - } - - private void OnSocketMessage(SocketHttpListener.MessageEventArgs e) - { - if (OnReceiveBytes != null) - { - OnReceiveBytes(e.RawData); - } - } - - public Task ConnectAsServerAsync() - { - return Task.CompletedTask; - } /// /// Gets or sets the state. /// /// The state. - public WebSocketState State => WebSocket.State; + public WebSocketState State => _webSocket.State; /// /// Sends the async. @@ -79,7 +48,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(byte[] bytes, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); + return _webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Binary, endOfMessage, cancellationToken); } /// @@ -91,7 +60,7 @@ namespace Emby.Server.Implementations.SocketSharp /// Task. public Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken) { - return WebSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); + return _webSocket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(text)), WebSocketMessageType.Text, endOfMessage, cancellationToken); } /// @@ -117,9 +86,6 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { _cancellationTokenSource.Cancel(); - - // TODO - // WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "bye", CancellationToken.None).GetAwaiter().GetResult(); } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 6e5190efdb..7a6144fb23 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -71,7 +71,6 @@ using Microsoft.Extensions.Logging; var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); var socket = new SharpWebSocket(webSocketContext, _logger); - await socket.ConnectAsServerAsync().ConfigureAwait(false); WebSocketConnected(new WebSocketConnectEventArgs { From 9c02e99e35fa6811502944b19dd1479750ff8f20 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:40:25 +0100 Subject: [PATCH 047/166] Undo some of the span abuse --- .../ApplicationHost.cs | 2 +- .../Emby.Server.Implementations.csproj | 1 + .../SocketSharp/WebSocketSharpRequest.cs | 25 ++++++------------- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a1ea179040..0b4a2fd30e 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1676,7 +1676,7 @@ namespace Emby.Server.Implementations LogErrorResponseBody = false, LogErrors = logPing, LogRequest = logPing, - TimeoutMs = 30000, + TimeoutMs = 5000, BufferContent = false, CancellationToken = cancellationToken diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index a4dac4a7f9..230e4892c7 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -40,6 +40,7 @@ + diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 146862e02b..35fff08216 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -273,11 +273,11 @@ namespace Emby.Server.Implementations.SocketSharp private static string GetQueryStringContentType(HttpRequest httpReq) { - string format = httpReq.Query["format"]; + ReadOnlySpan format = httpReq.Query["format"].ToString().AsSpan(); if (format == null) { const int formatMaxLength = 4; - string pi = httpReq.Path.ToString(); + ReadOnlySpan pi = httpReq.Path.ToString().AsSpan(); if (pi == null || pi.Length <= formatMaxLength) { return null; @@ -285,7 +285,7 @@ namespace Emby.Server.Implementations.SocketSharp if (pi[0] == '/') { - pi = pi.Substring(1); + pi = pi.Slice(1); } format = LeftPart(pi, '/'); @@ -296,11 +296,11 @@ namespace Emby.Server.Implementations.SocketSharp } format = LeftPart(format, '.'); - if (format.ToLower().Contains("json")) + if (format.Contains("json".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return "application/json"; } - else if (format.ToLower().Contains("xml")) + else if (format.Contains("xml".AsSpan(), StringComparison.OrdinalIgnoreCase)) { return "application/xml"; } @@ -308,25 +308,14 @@ namespace Emby.Server.Implementations.SocketSharp return null; } - public static string LeftPart(string strVal, char needle) + public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) { if (strVal == null) { return null; } - var pos = strVal.IndexOf(needle.ToString(), StringComparison.Ordinal); - return pos == -1 ? strVal : strVal.Substring(0, pos); - } - - public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle) - { - if (strVal == null) - { - return null; - } - - var pos = strVal.IndexOf(needle.ToString()); + var pos = strVal.IndexOf(needle); return pos == -1 ? strVal : strVal.Slice(0, pos); } From 5510e8ebee743a3d07571051660b514d6cce973c Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:53:59 +0100 Subject: [PATCH 048/166] Remove unused Cookies --- .../SocketSharp/WebSocketSharpRequest.cs | 19 ------------------- MediaBrowser.Model/Services/IRequest.cs | 2 -- 2 files changed, 21 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 35fff08216..38d0332308 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -412,25 +412,6 @@ namespace Emby.Server.Implementations.SocketSharp return path.Length > 1 ? path.TrimEnd('/') : "/"; } - private Dictionary cookies; - public IDictionary Cookies - { - get - { - if (cookies == null) - { - cookies = new Dictionary(); - foreach (var cookie in this.request.Cookies) - { - var httpCookie = cookie; - cookies[httpCookie.Key] = new Cookie(httpCookie.Key, httpCookie.Value, "", ""); - } - } - - return cookies; - } - } - public string UserAgent => request.Headers[HeaderNames.UserAgent]; public QueryParamCollection Headers => new QueryParamCollection(request.Headers); diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index b4ad24fec8..c7dccdb6fd 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -41,8 +41,6 @@ namespace MediaBrowser.Model.Services string UserAgent { get; } - IDictionary Cookies { get; } - /// /// The expected Response ContentType for this request /// From 848cfc32cc89327e16ff6ea281dc1d9b96cc4f7a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 26 Feb 2019 22:57:59 +0100 Subject: [PATCH 049/166] More cleanup --- .../HttpServer/HttpListenerHost.cs | 1 - .../SocketSharp/WebSocketSharpListener.cs | 13 ++----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 6b69fa0f4e..5b1cb433a9 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -803,7 +803,6 @@ namespace Emby.Server.Implementations.HttpServer private bool _disposed; private readonly object _disposeLock = new object(); - private readonly WebSocketSharpListener _websocketlistener; protected virtual void Dispose(bool disposing) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 7a6144fb23..77469244b9 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -17,8 +17,6 @@ using Microsoft.Extensions.Logging; { public class WebSocketSharpListener : IHttpListener { - private HttpListener _listener; - private readonly ILogger _logger; private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource(); @@ -86,14 +84,14 @@ using Microsoft.Extensions.Logging; do { - result = await webSocketContext.ReceiveAsync(buffer, CancellationToken.None); + result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); socket.OnReceiveBytes(buffer.Array); message.AddRange(buffer.Array.Take(result.Count)); } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); socket.OnReceiveBytes(message.ToArray()); await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, CancellationToken.None); + result.CloseStatusDescription, _disposeCancellationToken); socket.Dispose(); } else @@ -109,16 +107,9 @@ using Microsoft.Extensions.Logging; } } - public void Start(IEnumerable urlPrefixes) - { - throw new NotImplementedException(); - } - public Task Stop() { _disposeCancellationTokenSource.Cancel(); - _listener?.Close(); - return Task.CompletedTask; } From 5ccba7cf8aab7895d16beb9e2d9d24408a361ac1 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Wed, 27 Feb 2019 00:20:04 +0100 Subject: [PATCH 050/166] Move .azure to .ci --- {.azure => .ci}/azure-pipelines.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {.azure => .ci}/azure-pipelines.yml (100%) diff --git a/.azure/azure-pipelines.yml b/.ci/azure-pipelines.yml similarity index 100% rename from .azure/azure-pipelines.yml rename to .ci/azure-pipelines.yml From feb5b62ad4bf62e5d3585100b39643a41f27e626 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Wed, 27 Feb 2019 00:53:16 +0100 Subject: [PATCH 051/166] Enabled clean and updated the Github Connection Name --- .ci/azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index a14cf0aa3f..e5845c0efa 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -29,7 +29,7 @@ jobs: maxParallel: 2 steps: - checkout: self - clean: false + clean: true submodules: true persistCredentials: false @@ -159,7 +159,7 @@ jobs: - task: DownloadGitHubReleases@0 displayName: Download ABI compatibility check tool from GitHub inputs: - connection: GitHub (EraYaN) + connection: Jellyfin GitHub userRepository: EraYaN/dotnet-compatibility defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag #version: # Required when defaultVersionType != Latest From 77addb22835478a32c1133cfd69ae0da2ec5edea Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 07:32:36 +0100 Subject: [PATCH 052/166] Remove SocketHttpListener --- .../Emby.Server.Implementations.csproj | 2 - .../SocketSharp/SharpWebSocket.cs | 3 +- .../SocketSharp/WebSocketSharpRequest.cs | 1 - MediaBrowser.sln | 379 ++++--- SocketHttpListener/ByteOrder.cs | 17 - SocketHttpListener/CloseEventArgs.cs | 79 -- SocketHttpListener/CloseStatusCode.cs | 94 -- SocketHttpListener/CompressionMethod.cs | 23 - SocketHttpListener/ErrorEventArgs.cs | 42 - SocketHttpListener/Ext.cs | 946 ------------------ SocketHttpListener/Fin.cs | 8 - SocketHttpListener/HttpBase.cs | 70 -- SocketHttpListener/HttpResponse.cs | 122 --- SocketHttpListener/Mask.cs | 8 - SocketHttpListener/MessageEventArgs.cs | 84 -- SocketHttpListener/Net/CookieHelper.cs | 141 --- SocketHttpListener/Opcode.cs | 43 - SocketHttpListener/PayloadData.cs | 130 --- SocketHttpListener/Properties/AssemblyInfo.cs | 21 - SocketHttpListener/Rsv.cs | 8 - SocketHttpListener/SocketHttpListener.csproj | 18 - SocketHttpListener/SocketStream.cs | 74 -- SocketHttpListener/WebSocket.cs | 777 -------------- SocketHttpListener/WebSocketException.cs | 61 -- SocketHttpListener/WebSocketFrame.cs | 432 -------- 25 files changed, 188 insertions(+), 3395 deletions(-) delete mode 100644 SocketHttpListener/ByteOrder.cs delete mode 100644 SocketHttpListener/CloseEventArgs.cs delete mode 100644 SocketHttpListener/CloseStatusCode.cs delete mode 100644 SocketHttpListener/CompressionMethod.cs delete mode 100644 SocketHttpListener/ErrorEventArgs.cs delete mode 100644 SocketHttpListener/Ext.cs delete mode 100644 SocketHttpListener/Fin.cs delete mode 100644 SocketHttpListener/HttpBase.cs delete mode 100644 SocketHttpListener/HttpResponse.cs delete mode 100644 SocketHttpListener/Mask.cs delete mode 100644 SocketHttpListener/MessageEventArgs.cs delete mode 100644 SocketHttpListener/Net/CookieHelper.cs delete mode 100644 SocketHttpListener/Opcode.cs delete mode 100644 SocketHttpListener/PayloadData.cs delete mode 100644 SocketHttpListener/Properties/AssemblyInfo.cs delete mode 100644 SocketHttpListener/Rsv.cs delete mode 100644 SocketHttpListener/SocketHttpListener.csproj delete mode 100644 SocketHttpListener/SocketStream.cs delete mode 100644 SocketHttpListener/WebSocket.cs delete mode 100644 SocketHttpListener/WebSocketException.cs delete mode 100644 SocketHttpListener/WebSocketFrame.cs diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 230e4892c7..d4db5f2fcc 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -9,7 +9,6 @@ - @@ -40,7 +39,6 @@ - diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index 66c5cc334b..dcbfb80487 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.SocketSharp /// The web socket. private readonly WebSocket _webSocket; - private TaskCompletionSource _taskCompletionSource = new TaskCompletionSource(); private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); - private bool _disposed = false; + private bool _disposed; public SharpWebSocket(WebSocket socket, ILogger logger) { diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 38d0332308..53dce667ba 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; -using SocketHttpListener.Net; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 62ae58d732..5edc1a25a6 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,192 +1,187 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{1D74413B-E7CF-455B-B021-F52BDF881542}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - SharedVersion.cs = SharedVersion.cs - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.Build.0 = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} - EndGlobalSection - GlobalSection(AutomaticVersions) = postSolution - UpdateAssemblyVersion = True - UpdateAssemblyFileVersion = True - UpdateAssemblyInfoVersion = True - AssemblyVersionSettings = None.None.None.None - AssemblyFileVersionSettings = None.None.None.None - AssemblyInfoVersionSettings = None.None.None.None - UpdatePackageVersion = False - AssemblyInfoVersionType = SettingsVersion - InheritWinAppVersionFrom = None - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n - $0.DotNetNamingPolicy = $2 - $2.DirectoryNamespaceAssociation = PrefixedHierarchical - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal diff --git a/SocketHttpListener/ByteOrder.cs b/SocketHttpListener/ByteOrder.cs deleted file mode 100644 index c04150c74b..0000000000 --- a/SocketHttpListener/ByteOrder.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values that indicate whether the byte order is a Little-endian or Big-endian. - /// - public enum ByteOrder : byte - { - /// - /// Indicates a Little-endian. - /// - Little, - /// - /// Indicates a Big-endian. - /// - Big - } -} diff --git a/SocketHttpListener/CloseEventArgs.cs b/SocketHttpListener/CloseEventArgs.cs deleted file mode 100644 index c6460fd230..0000000000 --- a/SocketHttpListener/CloseEventArgs.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Text; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the WebSocket connection has been closed. - /// If you would like to get the reason for the close, you should access the or - /// property. - /// - public class CloseEventArgs : EventArgs - { - #region Private Fields - - private bool _clean; - private ushort _code; - private string _reason; - - #endregion - - #region Internal Constructors - - internal CloseEventArgs(PayloadData payload) - { - var data = payload.ApplicationData; - var len = data.Length; - _code = len > 1 - ? data.SubArray(0, 2).ToUInt16(ByteOrder.Big) - : (ushort)CloseStatusCode.NoStatusCode; - - _reason = len > 2 - ? GetUtf8String(data.SubArray(2, len - 2)) - : string.Empty; - } - - private static string GetUtf8String(byte[] bytes) - { - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - - #endregion - - #region Public Properties - - /// - /// Gets the status code for the close. - /// - /// - /// A that represents the status code for the close if any. - /// - public ushort Code => _code; - - /// - /// Gets the reason for the close. - /// - /// - /// A that represents the reason for the close if any. - /// - public string Reason => _reason; - - /// - /// Gets a value indicating whether the WebSocket connection has been closed cleanly. - /// - /// - /// true if the WebSocket connection has been closed cleanly; otherwise, false. - /// - public bool WasClean - { - get => _clean; - - internal set => _clean = value; - } - - #endregion - } -} diff --git a/SocketHttpListener/CloseStatusCode.cs b/SocketHttpListener/CloseStatusCode.cs deleted file mode 100644 index 428595bb09..0000000000 --- a/SocketHttpListener/CloseStatusCode.cs +++ /dev/null @@ -1,94 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the status code for the WebSocket connection close. - /// - /// - /// - /// The values of the status code are defined in - /// Section 7.4 - /// of RFC 6455. - /// - /// - /// "Reserved value" must not be set as a status code in a close control frame - /// by an endpoint. It's designated for use in applications expecting a status - /// code to indicate that the connection was closed due to the system grounds. - /// - /// - public enum CloseStatusCode : ushort - { - /// - /// Equivalent to close status 1000. - /// Indicates a normal close. - /// - Normal = 1000, - /// - /// Equivalent to close status 1001. - /// Indicates that an endpoint is going away. - /// - Away = 1001, - /// - /// Equivalent to close status 1002. - /// Indicates that an endpoint is terminating the connection due to a protocol error. - /// - ProtocolError = 1002, - /// - /// Equivalent to close status 1003. - /// Indicates that an endpoint is terminating the connection because it has received - /// an unacceptable type message. - /// - IncorrectData = 1003, - /// - /// Equivalent to close status 1004. - /// Still undefined. Reserved value. - /// - Undefined = 1004, - /// - /// Equivalent to close status 1005. - /// Indicates that no status code was actually present. Reserved value. - /// - NoStatusCode = 1005, - /// - /// Equivalent to close status 1006. - /// Indicates that the connection was closed abnormally. Reserved value. - /// - Abnormal = 1006, - /// - /// Equivalent to close status 1007. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that contains a data that isn't consistent with the type of the message. - /// - InconsistentData = 1007, - /// - /// Equivalent to close status 1008. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that violates its policy. - /// - PolicyViolation = 1008, - /// - /// Equivalent to close status 1009. - /// Indicates that an endpoint is terminating the connection because it has received - /// a message that is too big to process. - /// - TooBig = 1009, - /// - /// Equivalent to close status 1010. - /// Indicates that the client is terminating the connection because it has expected - /// the server to negotiate one or more extension, but the server didn't return them - /// in the handshake response. - /// - IgnoreExtension = 1010, - /// - /// Equivalent to close status 1011. - /// Indicates that the server is terminating the connection because it has encountered - /// an unexpected condition that prevented it from fulfilling the request. - /// - ServerError = 1011, - /// - /// Equivalent to close status 1015. - /// Indicates that the connection was closed due to a failure to perform a TLS handshake. - /// Reserved value. - /// - TlsHandshakeFailure = 1015 - } -} diff --git a/SocketHttpListener/CompressionMethod.cs b/SocketHttpListener/CompressionMethod.cs deleted file mode 100644 index d6bcd63d89..0000000000 --- a/SocketHttpListener/CompressionMethod.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the compression method used to compress the message on the WebSocket - /// connection. - /// - /// - /// The values of the compression method are defined in - /// Compression - /// Extensions for WebSocket. - /// - public enum CompressionMethod : byte - { - /// - /// Indicates non compression. - /// - None, - /// - /// Indicates using DEFLATE. - /// - Deflate - } -} diff --git a/SocketHttpListener/ErrorEventArgs.cs b/SocketHttpListener/ErrorEventArgs.cs deleted file mode 100644 index 9502d2a156..0000000000 --- a/SocketHttpListener/ErrorEventArgs.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the gets an error. - /// If you would like to get the error message, you should access the - /// property. - /// - public class ErrorEventArgs : EventArgs - { - #region Private Fields - - private string _message; - - #endregion - - #region Internal Constructors - - internal ErrorEventArgs(string message) - { - _message = message; - } - - #endregion - - #region Public Properties - - /// - /// Gets the error message. - /// - /// - /// A that represents the error message. - /// - public string Message => _message; - - #endregion - } -} diff --git a/SocketHttpListener/Ext.cs b/SocketHttpListener/Ext.cs deleted file mode 100644 index 697c5d5b70..0000000000 --- a/SocketHttpListener/Ext.cs +++ /dev/null @@ -1,946 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using MediaBrowser.Model.Services; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener -{ - /// - /// Provides a set of static methods for the websocket-sharp. - /// - public static class Ext - { - #region Private Const Fields - - private const string _tspecials = "()<>@,;:\\\"/[]?={} \t"; - - #endregion - - #region Private Methods - - private static MemoryStream compress(this Stream stream) - { - var output = new MemoryStream(); - if (stream.Length == 0) - return output; - - stream.Position = 0; - using (var ds = new DeflateStream(output, CompressionMode.Compress, true)) - { - stream.CopyTo(ds); - //ds.Close(); // "BFINAL" set to 1. - output.Position = 0; - - return output; - } - } - - private static byte[] decompress(this byte[] value) - { - if (value.Length == 0) - return value; - - using (var input = new MemoryStream(value)) - { - return input.decompressToArray(); - } - } - - private static MemoryStream decompress(this Stream stream) - { - var output = new MemoryStream(); - if (stream.Length == 0) - return output; - - stream.Position = 0; - using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true)) - { - ds.CopyTo(output, true); - return output; - } - } - - private static byte[] decompressToArray(this Stream stream) - { - using (var decomp = stream.decompress()) - { - return decomp.ToArray(); - } - } - - private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length) - { - var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false); - if (len < 1) - return buffer.SubArray(0, offset); - - var tmp = 0; - while (len < length) - { - tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false); - if (tmp < 1) - { - break; - } - - len += tmp; - } - - return len < length - ? buffer.SubArray(0, offset + len) - : buffer; - } - - private static async Task ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest) - { - var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false); - var len = bytes.Length; - dest.Write(bytes, 0, len); - - return len == offset + length; - } - - #endregion - - #region Internal Methods - - internal static async Task AppendAsync(this ushort code, string reason) - { - using (var buffer = new MemoryStream()) - { - var tmp = code.ToByteArrayInternally(ByteOrder.Big); - await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false); - if (reason != null && reason.Length > 0) - { - tmp = Encoding.UTF8.GetBytes(reason); - await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false); - } - - return buffer.ToArray(); - } - } - - internal static string CheckIfClosable(this WebSocketState state) - { - return state == WebSocketState.CloseSent - ? "While closing the WebSocket connection." - : state == WebSocketState.Closed - ? "The WebSocket connection has already been closed." - : null; - } - - internal static string CheckIfOpen(this WebSocketState state) - { - return state == WebSocketState.Connecting - ? "A WebSocket connection isn't established." - : state == WebSocketState.CloseSent - ? "While closing the WebSocket connection." - : state == WebSocketState.Closed - ? "The WebSocket connection has already been closed." - : null; - } - - internal static string CheckIfValidControlData(this byte[] data, string paramName) - { - return data.Length > 125 - ? string.Format("'{0}' length must be less.", paramName) - : null; - } - - internal static Stream Compress(this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.compress() - : stream; - } - - internal static bool Contains(this IEnumerable source, Func condition) - { - foreach (T elm in source) - if (condition(elm)) - return true; - - return false; - } - - internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition) - { - var readLen = 0; - var bufferLen = 256; - var buffer = new byte[bufferLen]; - while ((readLen = src.Read(buffer, 0, bufferLen)) > 0) - { - dest.Write(buffer, 0, readLen); - } - - if (setDefaultPosition) - dest.Position = 0; - } - - internal static byte[] Decompress(this byte[] value, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? value.decompress() - : value; - } - - internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.decompressToArray() - : stream.ToByteArray(); - } - - /// - /// Determines whether the specified equals the specified , - /// and invokes the specified Action<int> delegate at the same time. - /// - /// - /// true if equals ; - /// otherwise, false. - /// - /// - /// An to compare. - /// - /// - /// A to compare. - /// - /// - /// An Action<int> delegate that references the method(s) called at - /// the same time as comparing. An parameter to pass to - /// the method(s) is . - /// - /// - /// isn't between 0 and 255. - /// - internal static bool EqualsWith(this int value, char c, Action action) - { - if (value < 0 || value > 255) - throw new ArgumentOutOfRangeException(nameof(value)); - - action(value); - return value == c - 0; - } - - internal static string GetMessage(this CloseStatusCode code) - { - return code == CloseStatusCode.ProtocolError - ? "A WebSocket protocol error has occurred." - : code == CloseStatusCode.IncorrectData - ? "An incorrect data has been received." - : code == CloseStatusCode.Abnormal - ? "An exception has occurred." - : code == CloseStatusCode.InconsistentData - ? "An inconsistent data has been received." - : code == CloseStatusCode.PolicyViolation - ? "A policy violation has occurred." - : code == CloseStatusCode.TooBig - ? "A too big data has been received." - : code == CloseStatusCode.IgnoreExtension - ? "WebSocket client did not receive expected extension(s)." - : code == CloseStatusCode.ServerError - ? "WebSocket server got an internal error." - : code == CloseStatusCode.TlsHandshakeFailure - ? "An error has occurred while handshaking." - : string.Empty; - } - - internal static string GetNameInternal(this string nameAndValue, string separator) - { - var i = nameAndValue.IndexOf(separator); - return i > 0 - ? nameAndValue.Substring(0, i).Trim() - : null; - } - - internal static string GetValueInternal(this string nameAndValue, string separator) - { - var i = nameAndValue.IndexOf(separator); - return i >= 0 && i < nameAndValue.Length - 1 - ? nameAndValue.Substring(i + 1).Trim() - : null; - } - - internal static bool IsCompressionExtension(this string value, CompressionMethod method) - { - return value.StartsWith(method.ToExtensionString()); - } - - internal static bool IsPortNumber(this int value) - { - return value > 0 && value < 65536; - } - - internal static bool IsReserved(this ushort code) - { - return code == (ushort)CloseStatusCode.Undefined || - code == (ushort)CloseStatusCode.NoStatusCode || - code == (ushort)CloseStatusCode.Abnormal || - code == (ushort)CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsReserved(this CloseStatusCode code) - { - return code == CloseStatusCode.Undefined || - code == CloseStatusCode.NoStatusCode || - code == CloseStatusCode.Abnormal || - code == CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsText(this string value) - { - var len = value.Length; - for (var i = 0; i < len; i++) - { - char c = value[i]; - if (c < 0x20 && !"\r\n\t".Contains(c)) - return false; - - if (c == 0x7f) - return false; - - if (c == '\n' && ++i < len) - { - c = value[i]; - if (!" \t".Contains(c)) - return false; - } - } - - return true; - } - - internal static bool IsToken(this string value) - { - foreach (char c in value) - if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c)) - return false; - - return true; - } - - internal static string Quote(this string value) - { - return value.IsToken() - ? value - : string.Format("\"{0}\"", value.Replace("\"", "\\\"")); - } - - internal static Task ReadBytesAsync(this Stream stream, int length) - => stream.ReadBytesAsync(new byte[length], 0, length); - - internal static async Task ReadBytesAsync(this Stream stream, long length, int bufferLength) - { - using (var result = new MemoryStream()) - { - var count = length / bufferLength; - var rem = (int)(length % bufferLength); - - var buffer = new byte[bufferLength]; - var end = false; - for (long i = 0; i < count; i++) - { - if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false)) - { - end = true; - break; - } - } - - if (!end && rem > 0) - { - await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false); - } - - return result.ToArray(); - } - } - - internal static string RemovePrefix(this string value, params string[] prefixes) - { - var i = 0; - foreach (var prefix in prefixes) - { - if (value.StartsWith(prefix)) - { - i = prefix.Length; - break; - } - } - - return i > 0 - ? value.Substring(i) - : value; - } - - internal static T[] Reverse(this T[] array) - { - var len = array.Length; - T[] reverse = new T[len]; - - var end = len - 1; - for (var i = 0; i <= end; i++) - reverse[i] = array[end - i]; - - return reverse; - } - - internal static IEnumerable SplitHeaderValue( - this string value, params char[] separator) - { - var len = value.Length; - var separators = new string(separator); - - var buffer = new StringBuilder(32); - var quoted = false; - var escaped = false; - - char c; - for (var i = 0; i < len; i++) - { - c = value[i]; - if (c == '"') - { - if (escaped) - escaped = !escaped; - else - quoted = !quoted; - } - else if (c == '\\') - { - if (i < len - 1 && value[i + 1] == '"') - escaped = true; - } - else if (separators.Contains(c)) - { - if (!quoted) - { - yield return buffer.ToString(); - buffer.Length = 0; - - continue; - } - } - else - { - } - - buffer.Append(c); - } - - if (buffer.Length > 0) - yield return buffer.ToString(); - } - - internal static byte[] ToByteArray(this Stream stream) - { - using (var output = new MemoryStream()) - { - stream.Position = 0; - stream.CopyTo(output); - - return output.ToArray(); - } - } - - internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order) - { - var bytes = BitConverter.GetBytes(value); - if (!order.IsHostOrder()) - Array.Reverse(bytes); - - return bytes; - } - - internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order) - { - var bytes = BitConverter.GetBytes(value); - if (!order.IsHostOrder()) - Array.Reverse(bytes); - - return bytes; - } - - internal static string ToExtensionString( - this CompressionMethod method, params string[] parameters) - { - if (method == CompressionMethod.None) - return string.Empty; - - var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant()); - if (parameters == null || parameters.Length == 0) - return m; - - return string.Format("{0}; {1}", m, parameters.ToString("; ")); - } - - internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder) - { - src.ToHostOrder(srcOrder); - return BitConverter.ToUInt16(src, 0); - } - - internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder) - { - src.ToHostOrder(srcOrder); - return BitConverter.ToUInt64(src, 0); - } - - internal static string TrimEndSlash(this string value) - { - value = value.TrimEnd('/'); - return value.Length > 0 - ? value - : "/"; - } - - internal static string Unquote(this string value) - { - var start = value.IndexOf('\"'); - var end = value.LastIndexOf('\"'); - if (start < end) - value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\""); - - return value.Trim(); - } - - internal static void WriteBytes(this Stream stream, byte[] value) - { - using (var src = new MemoryStream(value)) - { - src.CopyTo(stream); - } - } - - #endregion - - #region Public Methods - - /// - /// Determines whether the specified contains any of characters - /// in the specified array of . - /// - /// - /// true if contains any of ; - /// otherwise, false. - /// - /// - /// A to test. - /// - /// - /// An array of that contains characters to find. - /// - public static bool Contains(this string value, params char[] chars) - { - return chars == null || chars.Length == 0 - ? true - : value == null || value.Length == 0 - ? false - : value.IndexOfAny(chars) != -1; - } - - /// - /// Determines whether the specified contains the entry - /// with the specified . - /// - /// - /// true if contains the entry - /// with ; otherwise, false. - /// - /// - /// A to test. - /// - /// - /// A that represents the key of the entry to find. - /// - public static bool Contains(this QueryParamCollection collection, string name) - { - return collection == null || collection.Count == 0 - ? false - : collection[name] != null; - } - - /// - /// Determines whether the specified contains the entry - /// with the specified both and . - /// - /// - /// true if contains the entry - /// with both and ; - /// otherwise, false. - /// - /// - /// A to test. - /// - /// - /// A that represents the key of the entry to find. - /// - /// - /// A that represents the value of the entry to find. - /// - public static bool Contains(this QueryParamCollection collection, string name, string value) - { - if (collection == null || collection.Count == 0) - return false; - - var values = collection[name]; - if (values == null) - return false; - - foreach (var v in values.Split(',')) - if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase)) - return true; - - return false; - } - - /// - /// Emits the specified EventHandler<TEventArgs> delegate - /// if it isn't . - /// - /// - /// An EventHandler<TEventArgs> to emit. - /// - /// - /// An from which emits this . - /// - /// - /// A TEventArgs that represents the event data. - /// - /// - /// The type of the event data generated by the event. - /// - public static void Emit( - this EventHandler eventHandler, object sender, TEventArgs e) - where TEventArgs : EventArgs - { - if (eventHandler != null) - eventHandler(sender, e); - } - - /// - /// Gets the description of the specified HTTP status . - /// - /// - /// A that represents the description of the HTTP status code. - /// - /// - /// One of enum values, indicates the HTTP status codes. - /// - public static string GetDescription(this HttpStatusCode code) - { - return ((int)code).GetStatusDescription(); - } - - /// - /// Gets the description of the specified HTTP status . - /// - /// - /// A that represents the description of the HTTP status code. - /// - /// - /// An that represents the HTTP status code. - /// - public static string GetStatusDescription(this int code) - { - switch (code) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-Uri Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 422: return "Unprocessable Entity"; - case 423: return "Locked"; - case 424: return "Failed Dependency"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "Http Version Not Supported"; - case 507: return "Insufficient Storage"; - } - - return string.Empty; - } - - /// - /// Determines whether the specified is host - /// (this computer architecture) byte order. - /// - /// - /// true if is host byte order; - /// otherwise, false. - /// - /// - /// One of the enum values, to test. - /// - public static bool IsHostOrder(this ByteOrder order) - { - // true : !(true ^ true) or !(false ^ false) - // false: !(true ^ false) or !(false ^ true) - return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little)); - } - - /// - /// Determines whether the specified is a predefined scheme. - /// - /// - /// true if is a predefined scheme; otherwise, false. - /// - /// - /// A to test. - /// - public static bool IsPredefinedScheme(this string value) - { - if (value == null || value.Length < 2) - return false; - - var c = value[0]; - if (c == 'h') - return value == "http" || value == "https"; - - if (c == 'w') - return value == "ws" || value == "wss"; - - if (c == 'f') - return value == "file" || value == "ftp"; - - if (c == 'n') - { - c = value[1]; - return c == 'e' - ? value == "news" || value == "net.pipe" || value == "net.tcp" - : value == "nntp"; - } - - return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto"); - } - - /// - /// Determines whether the specified is a URI string. - /// - /// - /// true if may be a URI string; otherwise, false. - /// - /// - /// A to test. - /// - public static bool MaybeUri(this string value) - { - if (value == null || value.Length == 0) - return false; - - var i = value.IndexOf(':'); - if (i == -1) - return false; - - if (i >= 10) - return false; - - return value.Substring(0, i).IsPredefinedScheme(); - } - - /// - /// Retrieves a sub-array from the specified . - /// A sub-array starts at the specified element position. - /// - /// - /// An array of T that receives a sub-array, or an empty array of T if any problems - /// with the parameters. - /// - /// - /// An array of T that contains the data to retrieve a sub-array. - /// - /// - /// An that contains the zero-based starting position of a sub-array - /// in . - /// - /// - /// An that contains the number of elements to retrieve a sub-array. - /// - /// - /// The type of elements in the . - /// - public static T[] SubArray(this T[] array, int startIndex, int length) - { - if (array == null || array.Length == 0) - return new T[0]; - - if (startIndex < 0 || length <= 0) - return new T[0]; - - if (startIndex + length > array.Length) - return new T[0]; - - if (startIndex == 0 && array.Length == length) - return array; - - T[] subArray = new T[length]; - Array.Copy(array, startIndex, subArray, 0, length); - - return subArray; - } - - /// - /// Converts the order of the specified array of to the host byte order. - /// - /// - /// An array of converted from . - /// - /// - /// An array of to convert. - /// - /// - /// One of the enum values, indicates the byte order of - /// . - /// - /// - /// is . - /// - public static void ToHostOrder(this byte[] src, ByteOrder srcOrder) - { - if (src == null) - { - throw new ArgumentNullException(nameof(src)); - } - - if (src.Length > 1 && !srcOrder.IsHostOrder()) - { - Array.Reverse(src); - } - } - - /// - /// Converts the specified to a that - /// concatenates the each element of across the specified - /// . - /// - /// - /// A converted from , - /// or if is empty. - /// - /// - /// An array of T to convert. - /// - /// - /// A that represents the separator string. - /// - /// - /// The type of elements in . - /// - /// - /// is . - /// - public static string ToString(this T[] array, string separator) - { - if (array == null) - throw new ArgumentNullException(nameof(array)); - - var len = array.Length; - if (len == 0) - return string.Empty; - - if (separator == null) - separator = string.Empty; - - var buff = new StringBuilder(64); - (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator)); - - buff.Append(array[len - 1].ToString()); - return buff.ToString(); - } - - /// - /// Executes the specified Action<int> delegate times. - /// - /// - /// An is the number of times to execute. - /// - /// - /// An Action<int> delegate that references the method(s) to execute. - /// An parameter to pass to the method(s) is the zero-based count of - /// iteration. - /// - public static void Times(this int n, Action action) - { - if (n > 0 && action != null) - for (int i = 0; i < n; i++) - action(i); - } - - /// - /// Converts the specified to a . - /// - /// - /// A converted from , or - /// if isn't successfully converted. - /// - /// - /// A to convert. - /// - public static Uri ToUri(this string uriString) - { - return Uri.TryCreate( - uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res) - ? res - : null; - } - - /// - /// URL-decodes the specified . - /// - /// - /// A that receives the decoded string, or the - /// if it's or empty. - /// - /// - /// A to decode. - /// - public static string UrlDecode(this string value) - { - return value == null || value.Length == 0 - ? value - : WebUtility.UrlDecode(value); - } - - #endregion - } -} diff --git a/SocketHttpListener/Fin.cs b/SocketHttpListener/Fin.cs deleted file mode 100644 index c8ffbf2b2f..0000000000 --- a/SocketHttpListener/Fin.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Fin : byte - { - More = 0x0, - Final = 0x1 - } -} diff --git a/SocketHttpListener/HttpBase.cs b/SocketHttpListener/HttpBase.cs deleted file mode 100644 index c386b93744..0000000000 --- a/SocketHttpListener/HttpBase.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Text; -using MediaBrowser.Model.Services; - -namespace SocketHttpListener -{ - internal abstract class HttpBase - { - #region Private Fields - - private QueryParamCollection _headers; - private Version _version; - - #endregion - - #region Protected Fields - - protected const string CrLf = "\r\n"; - - #endregion - - #region Protected Constructors - - protected HttpBase(Version version, QueryParamCollection headers) - { - _version = version; - _headers = headers; - } - - #endregion - - #region Public Properties - - public QueryParamCollection Headers => _headers; - - public Version ProtocolVersion => _version; - - #endregion - - #region Private Methods - - private static Encoding getEncoding(string contentType) - { - if (contentType == null || contentType.Length == 0) - return Encoding.UTF8; - - var i = contentType.IndexOf("charset=", StringComparison.Ordinal); - if (i == -1) - return Encoding.UTF8; - - var charset = contentType.Substring(i + 8); - i = charset.IndexOf(';'); - if (i != -1) - charset = charset.Substring(0, i).TrimEnd(); - - return Encoding.GetEncoding(charset.Trim('"')); - } - - #endregion - - #region Public Methods - - public byte[] ToByteArray() - { - return Encoding.UTF8.GetBytes(ToString()); - } - - #endregion - } -} diff --git a/SocketHttpListener/HttpResponse.cs b/SocketHttpListener/HttpResponse.cs deleted file mode 100644 index a33b942959..0000000000 --- a/SocketHttpListener/HttpResponse.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using System.Text; -using MediaBrowser.Model.Services; -using SocketHttpListener.Net; - -namespace SocketHttpListener -{ - // TODO what is the point of this class? - internal class HttpResponse : HttpBase - { - #region Private Fields - - private string _code; - private string _reason; - - #endregion - - #region Private Constructors - - private HttpResponse(string code, string reason, Version version, QueryParamCollection headers) - : base(version, headers) - { - _code = code; - _reason = reason; - } - - #endregion - - #region Internal Constructors - - internal HttpResponse(HttpStatusCode code) - : this(code, code.GetDescription()) - { - } - - internal HttpResponse(HttpStatusCode code, string reason) - : this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection()) - { - Headers["Server"] = "websocket-sharp/1.0"; - } - - #endregion - - #region Public Properties - - public CookieCollection Cookies => GetCookies(Headers, true); - - private static CookieCollection GetCookies(QueryParamCollection headers, bool response) - { - var name = response ? "Set-Cookie" : "Cookie"; - return headers == null || !headers.Contains(name) - ? new CookieCollection() - : CookieHelper.Parse(headers[name], response); - } - - public bool IsProxyAuthenticationRequired => _code == "407"; - - public bool IsUnauthorized => _code == "401"; - - public bool IsWebSocketResponse - { - get - { - var headers = Headers; - return ProtocolVersion > HttpVersion.Version10 && - _code == "101" && - headers.Contains("Upgrade", "websocket") && - headers.Contains("Connection", "Upgrade"); - } - } - - public string Reason => _reason; - - public string StatusCode => _code; - - #endregion - - #region Internal Methods - - internal static HttpResponse CreateCloseResponse(HttpStatusCode code) - { - var res = new HttpResponse(code); - res.Headers["Connection"] = "close"; - - return res; - } - - #endregion - - #region Public Methods - - public void SetCookies(CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var headers = Headers; - var sorted = cookies.OfType().OrderBy(i => i.Name).ToList(); - - foreach (var cookie in sorted) - headers.Add("Set-Cookie", cookie.ToString()); - } - - public override string ToString() - { - var output = new StringBuilder(64); - output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); - - var headers = Headers; - foreach (var key in headers.Keys) - output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append(CrLf); - - return output.ToString(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Mask.cs b/SocketHttpListener/Mask.cs deleted file mode 100644 index c56f5b08c0..0000000000 --- a/SocketHttpListener/Mask.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Mask : byte - { - Unmask = 0x0, - Mask = 0x1 - } -} diff --git a/SocketHttpListener/MessageEventArgs.cs b/SocketHttpListener/MessageEventArgs.cs deleted file mode 100644 index 8e2151cb7a..0000000000 --- a/SocketHttpListener/MessageEventArgs.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Text; - -namespace SocketHttpListener -{ - /// - /// Contains the event data associated with a event. - /// - /// - /// A event occurs when the receives - /// a text or binary data frame. - /// If you want to get the received data, you access the or - /// property. - /// - public class MessageEventArgs : EventArgs - { - #region Private Fields - - private string _data; - private Opcode _opcode; - private byte[] _rawData; - - #endregion - - #region Internal Constructors - - internal MessageEventArgs(Opcode opcode, byte[] data) - { - _opcode = opcode; - _rawData = data; - _data = convertToString(opcode, data); - } - - internal MessageEventArgs(Opcode opcode, PayloadData payload) - { - _opcode = opcode; - _rawData = payload.ApplicationData; - _data = convertToString(opcode, _rawData); - } - - #endregion - - #region Public Properties - - /// - /// Gets the received data as a . - /// - /// - /// A that contains the received data. - /// - public string Data => _data; - - /// - /// Gets the received data as an array of . - /// - /// - /// An array of that contains the received data. - /// - public byte[] RawData => _rawData; - - /// - /// Gets the type of the received data. - /// - /// - /// One of the values, indicates the type of the received data. - /// - public Opcode Type => _opcode; - - #endregion - - #region Private Methods - - private static string convertToString(Opcode opcode, byte[] data) - { - return data.Length == 0 - ? string.Empty - : opcode == Opcode.Text - ? Encoding.UTF8.GetString(data, 0, data.Length) - : opcode.ToString(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Net/CookieHelper.cs b/SocketHttpListener/Net/CookieHelper.cs deleted file mode 100644 index 3ad76ff238..0000000000 --- a/SocketHttpListener/Net/CookieHelper.cs +++ /dev/null @@ -1,141 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Net; -using System.Text; - -namespace SocketHttpListener.Net -{ - public static class CookieHelper - { - internal static CookieCollection Parse(string value, bool response) - { - return response - ? parseResponse(value) - : null; - } - - private static string[] splitCookieHeaderValue(string value) - { - return new List(value.SplitHeaderValue(',', ';')).ToArray(); - } - - private static CookieCollection parseResponse(string value) - { - var cookies = new CookieCollection(); - - Cookie cookie = null; - var pairs = splitCookieHeaderValue(value); - for (int i = 0; i < pairs.Length; i++) - { - var pair = pairs[i].Trim(); - if (pair.Length == 0) - continue; - - if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Version = int.Parse(pair.GetValueInternal("=").Trim('"')); - } - else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase)) - { - var buffer = new StringBuilder(pair.GetValueInternal("="), 32); - if (i < pairs.Length - 1) - buffer.AppendFormat(", {0}", pairs[++i].Trim()); - - if (!DateTime.TryParseExact( - buffer.ToString(), - new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, - new CultureInfo("en-US"), - DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, - out var expires)) - expires = DateTime.Now; - - if (cookie != null && cookie.Expires == DateTime.MinValue) - cookie.Expires = expires.ToLocalTime(); - } - else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase)) - { - var max = int.Parse(pair.GetValueInternal("=").Trim('"')); - var expires = DateTime.Now.AddSeconds((double)max); - if (cookie != null) - cookie.Expires = expires; - } - else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Path = pair.GetValueInternal("="); - } - else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Domain = pair.GetValueInternal("="); - } - else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase)) - { - var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase) - ? "\"\"" - : pair.GetValueInternal("="); - - if (cookie != null) - cookie.Port = port; - } - else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Comment = pair.GetValueInternal("=").UrlDecode(); - } - else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri(); - } - else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Discard = true; - } - else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.Secure = true; - } - else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase)) - { - if (cookie != null) - cookie.HttpOnly = true; - } - else - { - if (cookie != null) - cookies.Add(cookie); - - string name; - string val = string.Empty; - - var pos = pair.IndexOf('='); - if (pos == -1) - { - name = pair; - } - else if (pos == pair.Length - 1) - { - name = pair.Substring(0, pos).TrimEnd(' '); - } - else - { - name = pair.Substring(0, pos).TrimEnd(' '); - val = pair.Substring(pos + 1).TrimStart(' '); - } - - cookie = new Cookie(name, val); - } - } - - if (cookie != null) - cookies.Add(cookie); - - return cookies; - } - } -} diff --git a/SocketHttpListener/Opcode.cs b/SocketHttpListener/Opcode.cs deleted file mode 100644 index 69cf3f3728..0000000000 --- a/SocketHttpListener/Opcode.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace SocketHttpListener -{ - /// - /// Contains the values of the opcode that indicates the type of a WebSocket frame. - /// - /// - /// The values of the opcode are defined in - /// Section 5.2 of RFC 6455. - /// - public enum Opcode : byte - { - /// - /// Equivalent to numeric value 0. - /// Indicates a continuation frame. - /// - Cont = 0x0, - /// - /// Equivalent to numeric value 1. - /// Indicates a text frame. - /// - Text = 0x1, - /// - /// Equivalent to numeric value 2. - /// Indicates a binary frame. - /// - Binary = 0x2, - /// - /// Equivalent to numeric value 8. - /// Indicates a connection close frame. - /// - Close = 0x8, - /// - /// Equivalent to numeric value 9. - /// Indicates a ping frame. - /// - Ping = 0x9, - /// - /// Equivalent to numeric value 10. - /// Indicates a pong frame. - /// - Pong = 0xa - } -} diff --git a/SocketHttpListener/PayloadData.cs b/SocketHttpListener/PayloadData.cs deleted file mode 100644 index 6d15a6bcb1..0000000000 --- a/SocketHttpListener/PayloadData.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace SocketHttpListener -{ - internal class PayloadData : IEnumerable - { - #region Private Fields - - private byte[] _applicationData; - private byte[] _extensionData; - private bool _masked; - - #endregion - - #region Public Const Fields - - public const ulong MaxLength = long.MaxValue; - - #endregion - - #region Public Constructors - - public PayloadData() - : this(new byte[0], new byte[0], false) - { - } - - public PayloadData(byte[] applicationData) - : this(new byte[0], applicationData, false) - { - } - - public PayloadData(string applicationData) - : this(new byte[0], Encoding.UTF8.GetBytes(applicationData), false) - { - } - - public PayloadData(byte[] applicationData, bool masked) - : this(new byte[0], applicationData, masked) - { - } - - public PayloadData(byte[] extensionData, byte[] applicationData, bool masked) - { - _extensionData = extensionData; - _applicationData = applicationData; - _masked = masked; - } - - #endregion - - #region Internal Properties - - internal bool ContainsReservedCloseStatusCode => - _applicationData.Length > 1 && - _applicationData.SubArray(0, 2).ToUInt16(ByteOrder.Big).IsReserved(); - - #endregion - - #region Public Properties - - public byte[] ApplicationData => _applicationData; - - public byte[] ExtensionData => _extensionData; - - public bool IsMasked => _masked; - - public ulong Length => (ulong)(_extensionData.Length + _applicationData.Length); - - #endregion - - #region Private Methods - - private static void mask(byte[] src, byte[] key) - { - for (long i = 0; i < src.Length; i++) - src[i] = (byte)(src[i] ^ key[i % 4]); - } - - #endregion - - #region Public Methods - - public IEnumerator GetEnumerator() - { - foreach (byte b in _extensionData) - yield return b; - - foreach (byte b in _applicationData) - yield return b; - } - - public void Mask(byte[] maskingKey) - { - if (_extensionData.Length > 0) - mask(_extensionData, maskingKey); - - if (_applicationData.Length > 0) - mask(_applicationData, maskingKey); - - _masked = !_masked; - } - - public byte[] ToByteArray() - { - return _extensionData.Length > 0 - ? new List(this).ToArray() - : _applicationData; - } - - public override string ToString() - { - return BitConverter.ToString(ToByteArray()); - } - - #endregion - - #region Explicitly Implemented Interface Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} diff --git a/SocketHttpListener/Properties/AssemblyInfo.cs b/SocketHttpListener/Properties/AssemblyInfo.cs deleted file mode 100644 index a69bd176f7..0000000000 --- a/SocketHttpListener/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SocketHttpListener")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] diff --git a/SocketHttpListener/Rsv.cs b/SocketHttpListener/Rsv.cs deleted file mode 100644 index 87283791ec..0000000000 --- a/SocketHttpListener/Rsv.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace SocketHttpListener -{ - internal enum Rsv : byte - { - Off = 0x0, - On = 0x1 - } -} diff --git a/SocketHttpListener/SocketHttpListener.csproj b/SocketHttpListener/SocketHttpListener.csproj deleted file mode 100644 index e700540a91..0000000000 --- a/SocketHttpListener/SocketHttpListener.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - netstandard2.0 - true - false - - - diff --git a/SocketHttpListener/SocketStream.cs b/SocketHttpListener/SocketStream.cs deleted file mode 100644 index f51fde97ef..0000000000 --- a/SocketHttpListener/SocketStream.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.IO; -using System.Net.Sockets; - -namespace SocketHttpListener -{ - public class SocketStream : Stream - { - private readonly Socket _socket; - - public SocketStream(Socket socket, bool ownsSocket) - { - _socket = socket; - } - - public override void Flush() - { - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _socket.Send(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - _socket.EndSend(asyncResult); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _socket.Receive(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return _socket.EndReceive(asyncResult); - } - } -} diff --git a/SocketHttpListener/WebSocket.cs b/SocketHttpListener/WebSocket.cs deleted file mode 100644 index afce871fd6..0000000000 --- a/SocketHttpListener/WebSocket.cs +++ /dev/null @@ -1,777 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using WebSocketState = System.Net.WebSockets.WebSocketState; - -namespace SocketHttpListener -{ - /// - /// Implements the WebSocket interface. - /// - /// - /// The WebSocket class provides a set of methods and properties for two-way communication using - /// the WebSocket protocol (RFC 6455). - /// - public class WebSocket : IDisposable - { - #region Private Fields - - private Action _closeContext; - private CompressionMethod _compression; - private WebSocketContext _context; - private CookieCollection _cookies; - private AutoResetEvent _exitReceiving; - private object _forConn; - private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1); - private object _forMessageEventQueue; - private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1); - private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - private Queue _messageEventQueue; - private string _protocol; - private volatile WebSocketState _readyState; - private AutoResetEvent _receivePong; - private bool _secure; - private Stream _stream; - private const string _version = "13"; - - #endregion - - #region Internal Fields - - internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14. - - #endregion - - #region Internal Constructors - - // As server - internal WebSocket(string protocol) - { - _protocol = protocol; - } - - public void SetContext(HttpListenerWebSocketContext context, Action closeContextFn, Stream stream) - { - _context = context; - - _closeContext = closeContextFn; - _secure = context.IsSecureConnection; - _stream = stream; - - init(); - } - - // In the .NET Framework, this pulls the value from a P/Invoke. Here we just hardcode it to a reasonable default. - public static TimeSpan DefaultKeepAliveInterval => TimeSpan.FromSeconds(30); - - #endregion - - /// - /// Gets the state of the WebSocket connection. - /// - /// - /// One of the enum values, indicates the state of the WebSocket - /// connection. The default value is . - /// - public WebSocketState ReadyState => _readyState; - - #region Public Events - - /// - /// Occurs when the WebSocket connection has been closed. - /// - public event EventHandler OnClose; - - /// - /// Occurs when the gets an error. - /// - public event EventHandler OnError; - - /// - /// Occurs when the receives a message. - /// - public event EventHandler OnMessage; - - /// - /// Occurs when the WebSocket connection has been established. - /// - public event EventHandler OnOpen; - - #endregion - - #region Private Methods - - private async Task CloseAsync(CloseStatusCode code, string reason, bool wait) - { - await CloseAsync(new PayloadData( - await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)), - !code.IsReserved(), - wait).ConfigureAwait(false); - } - - private async Task CloseAsync(PayloadData payload, bool send, bool wait) - { - lock (_forConn) - { - if (_readyState == WebSocketState.CloseSent || _readyState == WebSocketState.Closed) - { - return; - } - - _readyState = WebSocketState.CloseSent; - } - - var e = new CloseEventArgs(payload) - { - WasClean = await CloseHandshakeAsync( - send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null, - wait ? 1000 : 0).ConfigureAwait(false) - }; - - _readyState = WebSocketState.Closed; - try - { - OnClose.Emit(this, e); - } - catch (Exception ex) - { - error("An exception has occurred while OnClose.", ex); - } - } - - private async Task CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout) - { - var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false); - var received = - millisecondsTimeout == 0 || - (sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout)); - - closeServerResources(); - - if (_receivePong != null) - { - _receivePong.Dispose(); - _receivePong = null; - } - - if (_exitReceiving != null) - { - _exitReceiving.Dispose(); - _exitReceiving = null; - } - - var result = sent && received; - - return result; - } - - // As server - private void closeServerResources() - { - if (_closeContext == null) - return; - - try - { - _closeContext(); - } - catch (SocketException) - { - // it could be unable to send the handshake response - } - - _closeContext = null; - _stream = null; - _context = null; - } - - private async Task ConcatenateFragmentsIntoAsync(Stream dest) - { - while (true) - { - var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false); - if (frame.IsFinal) - { - /* FINAL */ - - // CONT - if (frame.IsContinuation) - { - dest.WriteBytes(frame.PayloadData.ApplicationData); - break; - } - - // PING - if (frame.IsPing) - { - processPingFrame(frame); - continue; - } - - // PONG - if (frame.IsPong) - { - processPongFrame(frame); - continue; - } - - // CLOSE - if (frame.IsClose) - return await ProcessCloseFrameAsync(frame).ConfigureAwait(false); - } - else - { - /* MORE */ - - // CONT - if (frame.IsContinuation) - { - dest.WriteBytes(frame.PayloadData.ApplicationData); - continue; - } - } - - // ? - return await ProcessUnsupportedFrameAsync( - frame, - CloseStatusCode.IncorrectData, - "An incorrect data has been received while receiving fragmented data.").ConfigureAwait(false); - } - - return true; - } - - // As server - private HttpResponse createHandshakeCloseResponse(HttpStatusCode code) - { - var res = HttpResponse.CreateCloseResponse(code); - res.Headers["Sec-WebSocket-Version"] = _version; - - return res; - } - - private MessageEventArgs dequeueFromMessageEventQueue() - { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0 - ? _messageEventQueue.Dequeue() - : null; - } - - private void enqueueToMessageEventQueue(MessageEventArgs e) - { - lock (_forMessageEventQueue) - _messageEventQueue.Enqueue(e); - } - - private void error(string message, Exception exception) - { - try - { - if (exception != null) - { - message += ". Exception.Message: " + exception.Message; - } - OnError.Emit(this, new ErrorEventArgs(message)); - } - catch (Exception) - { - } - } - - private void error(string message) - { - try - { - OnError.Emit(this, new ErrorEventArgs(message)); - } - catch (Exception) - { - } - } - - private void init() - { - _compression = CompressionMethod.None; - _cookies = new CookieCollection(); - _forConn = new object(); - _messageEventQueue = new Queue(); - _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; - } - - private async Task OpenAsync() - { - try - { - startReceiving(); - - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while opening.").ConfigureAwait(false); - } - - await _forEvent.WaitAsync().ConfigureAwait(false); - try - { - OnOpen?.Invoke(this, EventArgs.Empty); - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while OnOpen.").ConfigureAwait(false); - } - finally - { - _forEvent.Release(); - } - } - - private async Task ProcessCloseFrameAsync(WebSocketFrame frame) - { - var payload = frame.PayloadData; - await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false); - - return false; - } - - private bool processDataFrame(WebSocketFrame frame) - { - var e = frame.IsCompressed - ? new MessageEventArgs( - frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) - : new MessageEventArgs(frame.Opcode, frame.PayloadData); - - enqueueToMessageEventQueue(e); - return true; - } - - private async Task ProcessExceptionAsync(Exception exception, string message) - { - var code = CloseStatusCode.Abnormal; - var reason = message; - if (exception is WebSocketException) - { - var wsex = (WebSocketException)exception; - code = wsex.Code; - reason = wsex.Message; - } - - error(message ?? code.GetMessage(), exception); - if (_readyState == WebSocketState.Connecting) - { - await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false); - } - else - { - await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false); - } - } - - private Task ProcessFragmentedFrameAsync(WebSocketFrame frame) - { - return frame.IsContinuation // Not first fragment - ? Task.FromResult(true) - : ProcessFragmentsAsync(frame); - } - - private async Task ProcessFragmentsAsync(WebSocketFrame first) - { - using (var buff = new MemoryStream()) - { - buff.WriteBytes(first.PayloadData.ApplicationData); - if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false)) - { - return false; - } - - byte[] data; - if (_compression != CompressionMethod.None) - { - data = buff.DecompressToArray(_compression); - } - else - { - data = buff.ToArray(); - } - - enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data)); - return true; - } - } - - private bool processPingFrame(WebSocketFrame frame) - { - return true; - } - - private bool processPongFrame(WebSocketFrame frame) - { - _receivePong.Set(); - - return true; - } - - private async Task ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason) - { - await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false); - - return false; - } - - private Task ProcessWebSocketFrameAsync(WebSocketFrame frame) - { - // TODO: @bond change to if/else chain - return frame.IsCompressed && _compression == CompressionMethod.None - ? ProcessUnsupportedFrameAsync( - frame, - CloseStatusCode.IncorrectData, - "A compressed data has been received without available decompression method.") - : frame.IsFragmented - ? ProcessFragmentedFrameAsync(frame) - : frame.IsData - ? Task.FromResult(processDataFrame(frame)) - : frame.IsPing - ? Task.FromResult(processPingFrame(frame)) - : frame.IsPong - ? Task.FromResult(processPongFrame(frame)) - : frame.IsClose - ? ProcessCloseFrameAsync(frame) - : ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null); - } - - private async Task SendAsync(Opcode opcode, Stream stream) - { - await _forSend.WaitAsync().ConfigureAwait(false); - try - { - var src = stream; - var compressed = false; - var sent = false; - try - { - if (_compression != CompressionMethod.None) - { - stream = stream.Compress(_compression); - compressed = true; - } - - sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false); - if (!sent) - error("Sending a data has been interrupted."); - } - catch (Exception ex) - { - error("An exception has occurred while sending a data.", ex); - } - finally - { - if (compressed) - stream.Dispose(); - - src.Dispose(); - } - - return sent; - } - finally - { - _forSend.Release(); - } - } - - private async Task SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed) - { - var len = stream.Length; - - /* Not fragmented */ - - if (len == 0) - return await SendAsync(Fin.Final, opcode, mask, new byte[0], compressed).ConfigureAwait(false); - - var quo = len / FragmentLength; - var rem = (int)(len % FragmentLength); - - byte[] buff = null; - if (quo == 0) - { - buff = new byte[rem]; - return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && - await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); - } - - buff = new byte[FragmentLength]; - if (quo == 1 && rem == 0) - return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength && - await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false); - - /* Send fragmented */ - - // Begin - if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || - !await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false)) - return false; - - var n = rem == 0 ? quo - 2 : quo - 1; - for (long i = 0; i < n; i++) - if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength || - !await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false)) - return false; - - // End - if (rem == 0) - rem = FragmentLength; - else - buff = new byte[rem]; - - return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem && - await SendAsync(Fin.Final, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false); - } - - private Task SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) - { - lock (_forConn) - { - if (_readyState != WebSocketState.Open) - { - return Task.FromResult(false); - } - - return WriteBytesAsync( - WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray()); - } - } - - // As server - private Task SendHttpResponseAsync(HttpResponse response) - => WriteBytesAsync(response.ToByteArray()); - - private void startReceiving() - { - if (_messageEventQueue.Count > 0) - { - _messageEventQueue.Clear(); - } - - _exitReceiving = new AutoResetEvent(false); - _receivePong = new AutoResetEvent(false); - - Action receive = null; - receive = async () => await WebSocketFrame.ReadAsync( - _stream, - true, - async frame => - { - if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed) - { - receive(); - - if (!frame.IsData) - { - return; - } - - await _forEvent.WaitAsync().ConfigureAwait(false); - - try - { - var e = dequeueFromMessageEventQueue(); - if (e != null && _readyState == WebSocketState.Open) - { - OnMessage.Emit(this, e); - } - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false); - } - finally - { - _forEvent.Release(); - } - - } - else if (_exitReceiving != null) - { - _exitReceiving.Set(); - } - }, - async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false); - - receive(); - } - - private async Task WriteBytesAsync(byte[] data) - { - try - { - await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false); - return true; - } - catch (Exception) - { - return false; - } - } - - #endregion - - #region Internal Methods - - // As server - internal async Task CloseAsync(HttpResponse response) - { - _readyState = WebSocketState.CloseSent; - await SendHttpResponseAsync(response).ConfigureAwait(false); - - closeServerResources(); - - _readyState = WebSocketState.Closed; - } - - // As server - internal Task CloseAsync(HttpStatusCode code) - => CloseAsync(createHandshakeCloseResponse(code)); - - // As server - public async Task ConnectAsServer() - { - try - { - _readyState = WebSocketState.Open; - await OpenAsync().ConfigureAwait(false); - } - catch (Exception ex) - { - await ProcessExceptionAsync(ex, "An exception has occurred while connecting.").ConfigureAwait(false); - } - } - - #endregion - - #region Public Methods - - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - public Task CloseAsync() - { - var msg = _readyState.CheckIfClosable(); - if (msg != null) - { - error(msg); - - return Task.CompletedTask; - } - - var send = _readyState == WebSocketState.Open; - return CloseAsync(new PayloadData(), send, send); - } - - /// - /// Closes the WebSocket connection with the specified - /// and , and releases all associated resources. - /// - /// - /// This method emits a event if the size - /// of is greater than 123 bytes. - /// - /// - /// One of the enum values, represents the status code - /// indicating the reason for the close. - /// - /// - /// A that represents the reason for the close. - /// - public async Task CloseAsync(CloseStatusCode code, string reason) - { - byte[] data = null; - var msg = _readyState.CheckIfClosable() ?? - (data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason"); - - if (msg != null) - { - error(msg); - - return; - } - - var send = _readyState == WebSocketState.Open && !code.IsReserved(); - await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false); - } - - /// - /// Sends a binary asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - public Task SendAsync(byte[] data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var msg = _readyState.CheckIfOpen(); - if (msg != null) - { - throw new Exception(msg); - } - - return SendAsync(Opcode.Binary, new MemoryStream(data)); - } - - /// - /// Sends a text asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - public Task SendAsync(string data) - { - if (data == null) - { - throw new ArgumentNullException(nameof(data)); - } - - var msg = _readyState.CheckIfOpen(); - if (msg != null) - { - throw new Exception(msg); - } - - return SendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data))); - } - - #endregion - - #region Explicit Interface Implementation - - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - /// - /// This method closes the WebSocket connection with . - /// - void IDisposable.Dispose() - { - CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult(); - } - - #endregion - } -} diff --git a/SocketHttpListener/WebSocketException.cs b/SocketHttpListener/WebSocketException.cs deleted file mode 100644 index e86c46d0f5..0000000000 --- a/SocketHttpListener/WebSocketException.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; - -namespace SocketHttpListener -{ - /// - /// The exception that is thrown when a gets a fatal error. - /// - public class WebSocketException : Exception - { - #region Internal Constructors - - internal WebSocketException() - : this(CloseStatusCode.Abnormal, null, null) - { - } - - internal WebSocketException(string message) - : this(CloseStatusCode.Abnormal, message, null) - { - } - - internal WebSocketException(CloseStatusCode code) - : this(code, null, null) - { - } - - internal WebSocketException(string message, Exception innerException) - : this(CloseStatusCode.Abnormal, message, innerException) - { - } - - internal WebSocketException(CloseStatusCode code, string message) - : this(code, message, null) - { - } - - internal WebSocketException(CloseStatusCode code, string message, Exception innerException) - : base(message ?? code.GetMessage(), innerException) - { - Code = code; - } - - #endregion - - #region Public Properties - - /// - /// Gets the status code indicating the cause for the exception. - /// - /// - /// One of the enum values, represents the status code indicating - /// the cause for the exception. - /// - public CloseStatusCode Code - { - get; private set; - } - - #endregion - } -} diff --git a/SocketHttpListener/WebSocketFrame.cs b/SocketHttpListener/WebSocketFrame.cs deleted file mode 100644 index 8ec64026bd..0000000000 --- a/SocketHttpListener/WebSocketFrame.cs +++ /dev/null @@ -1,432 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; - -namespace SocketHttpListener -{ - internal class WebSocketFrame : IEnumerable - { - #region Private Fields - - private byte[] _extPayloadLength; - private Fin _fin; - private Mask _mask; - private byte[] _maskingKey; - private Opcode _opcode; - private PayloadData _payloadData; - private byte _payloadLength; - private Rsv _rsv1; - private Rsv _rsv2; - private Rsv _rsv3; - - #endregion - - #region Internal Fields - - internal static readonly byte[] EmptyUnmaskPingData; - - #endregion - - #region Static Constructor - - static WebSocketFrame() - { - EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray(); - } - - #endregion - - #region Private Constructors - - private WebSocketFrame() - { - } - - #endregion - - #region Internal Constructors - - internal WebSocketFrame(Opcode opcode, PayloadData payload) - : this(Fin.Final, opcode, Mask.Mask, payload, false) - { - } - - internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload) - : this(Fin.Final, opcode, mask, payload, false) - { - } - - internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload) - : this(fin, opcode, mask, payload, false) - { - } - - internal WebSocketFrame( - Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed) - { - _fin = fin; - _rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off; - _rsv2 = Rsv.Off; - _rsv3 = Rsv.Off; - _opcode = opcode; - _mask = mask; - - var len = payload.Length; - if (len < 126) - { - _payloadLength = (byte)len; - _extPayloadLength = new byte[0]; - } - else if (len < 0x010000) - { - _payloadLength = (byte)126; - _extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big); - } - else - { - _payloadLength = (byte)127; - _extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big); - } - - if (mask == Mask.Mask) - { - _maskingKey = createMaskingKey(); - payload.Mask(_maskingKey); - } - else - { - _maskingKey = new byte[0]; - } - - _payloadData = payload; - } - - #endregion - - #region Public Properties - - public byte[] ExtendedPayloadLength => _extPayloadLength; - - public Fin Fin => _fin; - - public bool IsBinary => _opcode == Opcode.Binary; - - public bool IsClose => _opcode == Opcode.Close; - - public bool IsCompressed => _rsv1 == Rsv.On; - - public bool IsContinuation => _opcode == Opcode.Cont; - - public bool IsControl => _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong; - - public bool IsData => _opcode == Opcode.Binary || _opcode == Opcode.Text; - - public bool IsFinal => _fin == Fin.Final; - - public bool IsFragmented => _fin == Fin.More || _opcode == Opcode.Cont; - - public bool IsMasked => _mask == Mask.Mask; - - public bool IsPerMessageCompressed => (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On; - - public bool IsPing => _opcode == Opcode.Ping; - - public bool IsPong => _opcode == Opcode.Pong; - - public bool IsText => _opcode == Opcode.Text; - - public ulong Length => 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length; - - public Mask Mask => _mask; - - public byte[] MaskingKey => _maskingKey; - - public Opcode Opcode => _opcode; - - public PayloadData PayloadData => _payloadData; - - public byte PayloadLength => _payloadLength; - - public Rsv Rsv1 => _rsv1; - - public Rsv Rsv2 => _rsv2; - - public Rsv Rsv3 => _rsv3; - - #endregion - - #region Private Methods - - private byte[] createMaskingKey() - { - var key = new byte[4]; - var rand = new Random(); - rand.NextBytes(key); - - return key; - } - - private static bool isControl(Opcode opcode) - { - return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong; - } - - private static bool isData(Opcode opcode) - { - return opcode == Opcode.Text || opcode == Opcode.Binary; - } - - private static async Task ReadAsync(byte[] header, Stream stream, bool unmask) - { - /* Header */ - - // FIN - var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More; - // RSV1 - var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off; - // RSV2 - var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off; - // RSV3 - var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off; - // Opcode - var opcode = (Opcode)(header[0] & 0x0f); - // MASK - var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask; - // Payload Length - var payloadLen = (byte)(header[1] & 0x7f); - - // Check if correct frame. - var incorrect = isControl(opcode) && fin == Fin.More - ? "A control frame is fragmented." - : !isData(opcode) && rsv1 == Rsv.On - ? "A non data frame is compressed." - : null; - - if (incorrect != null) - throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect); - - // Check if consistent frame. - if (isControl(opcode) && payloadLen > 125) - throw new WebSocketException( - CloseStatusCode.InconsistentData, - "The length of payload data of a control frame is greater than 125 bytes."); - - var frame = new WebSocketFrame(); - frame._fin = fin; - frame._rsv1 = rsv1; - frame._rsv2 = rsv2; - frame._rsv3 = rsv3; - frame._opcode = opcode; - frame._mask = mask; - frame._payloadLength = payloadLen; - - /* Extended Payload Length */ - - var size = payloadLen < 126 - ? 0 - : payloadLen == 126 - ? 2 - : 8; - - var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty(); - if (size > 0 && extPayloadLen.Length != size) - throw new WebSocketException( - "The 'Extended Payload Length' of a frame cannot be read from the data source."); - - frame._extPayloadLength = extPayloadLen; - - /* Masking Key */ - - var masked = mask == Mask.Mask; - var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty(); - if (masked && maskingKey.Length != 4) - throw new WebSocketException( - "The 'Masking Key' of a frame cannot be read from the data source."); - - frame._maskingKey = maskingKey; - - /* Payload Data */ - - ulong len = payloadLen < 126 - ? payloadLen - : payloadLen == 126 - ? extPayloadLen.ToUInt16(ByteOrder.Big) - : extPayloadLen.ToUInt64(ByteOrder.Big); - - byte[] data = null; - if (len > 0) - { - // Check if allowable payload data length. - if (payloadLen > 126 && len > PayloadData.MaxLength) - throw new WebSocketException( - CloseStatusCode.TooBig, - "The length of 'Payload Data' of a frame is greater than the allowable length."); - - data = payloadLen > 126 - ? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false) - : await stream.ReadBytesAsync((int)len).ConfigureAwait(false); - - //if (data.LongLength != (long)len) - // throw new WebSocketException( - // "The 'Payload Data' of a frame cannot be read from the data source."); - } - else - { - data = Array.Empty(); - } - - var payload = new PayloadData(data, masked); - if (masked && unmask) - { - payload.Mask(maskingKey); - frame._mask = Mask.Unmask; - frame._maskingKey = Array.Empty(); - } - - frame._payloadData = payload; - return frame; - } - - #endregion - - #region Internal Methods - - internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data) - { - return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data)); - } - - internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload) - { - return new WebSocketFrame(Opcode.Close, mask, payload); - } - - internal static async Task CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason) - { - return new WebSocketFrame( - Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false))); - } - - internal static WebSocketFrame CreatePingFrame(Mask mask) - { - return new WebSocketFrame(Opcode.Ping, mask, new PayloadData()); - } - - internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data) - { - return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data)); - } - - internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload) - { - return new WebSocketFrame(Opcode.Pong, mask, payload); - } - - internal static WebSocketFrame CreateWebSocketFrame( - Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed) - { - return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed); - } - - internal static Task ReadAsync(Stream stream) - => ReadAsync(stream, true); - - internal static async Task ReadAsync(Stream stream, bool unmask) - { - var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); - if (header.Length != 2) - { - throw new WebSocketException( - "The header part of a frame cannot be read from the data source."); - } - - return await ReadAsync(header, stream, unmask).ConfigureAwait(false); - } - - internal static async Task ReadAsync( - Stream stream, bool unmask, Action completed, Action error) - { - try - { - var header = await stream.ReadBytesAsync(2).ConfigureAwait(false); - if (header.Length != 2) - { - throw new WebSocketException( - "The header part of a frame cannot be read from the data source."); - } - - var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false); - completed?.Invoke(frame); - } - catch (Exception ex) - { - error.Invoke(ex); - } - } - - #endregion - - #region Public Methods - - public IEnumerator GetEnumerator() - { - foreach (var b in ToByteArray()) - yield return b; - } - - public void Print(bool dumped) - { - //Console.WriteLine(dumped ? dump(this) : print(this)); - } - - public byte[] ToByteArray() - { - using (var buff = new MemoryStream()) - { - var header = (int)_fin; - header = (header << 1) + (int)_rsv1; - header = (header << 1) + (int)_rsv2; - header = (header << 1) + (int)_rsv3; - header = (header << 4) + (int)_opcode; - header = (header << 1) + (int)_mask; - header = (header << 7) + (int)_payloadLength; - buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2); - - if (_payloadLength > 125) - buff.Write(_extPayloadLength, 0, _extPayloadLength.Length); - - if (_mask == Mask.Mask) - buff.Write(_maskingKey, 0, _maskingKey.Length); - - if (_payloadLength > 0) - { - var payload = _payloadData.ToByteArray(); - if (_payloadLength < 127) - buff.Write(payload, 0, payload.Length); - else - buff.WriteBytes(payload); - } - - return buff.ToArray(); - } - } - - public override string ToString() - { - return BitConverter.ToString(ToByteArray()); - } - - #endregion - - #region Explicitly Implemented Interface Members - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - #endregion - } -} From 25d3d0b731db793e4afc39ff40e0b6a1a27d6ad8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 08:02:32 +0100 Subject: [PATCH 053/166] Remove some unused stuff --- .../SocketSharp/RequestMono.cs | 17 ----------------- .../SocketSharp/WebSocketSharpRequest.cs | 17 ----------------- MediaBrowser.Model/Services/IRequest.cs | 10 ---------- 3 files changed, 44 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index a8142aef68..113f76b10b 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -105,14 +105,6 @@ namespace Emby.Server.Implementations.SocketSharp await LoadWwwForm(form).ConfigureAwait(false); } -#if NET_4_0 - if (validateRequestNewMode && !checked_form) { - // Setting this before calling the validator prevents - // possible endless recursion - checked_form = true; - ValidateNameValueCollection("Form", query_string_nvc, RequestValidationSource.Form); - } else -#endif if (validate_form && !checked_form) { checked_form = true; @@ -129,8 +121,6 @@ namespace Emby.Server.Implementations.SocketSharp protected bool validate_cookies { get; set; } protected bool validate_query_string { get; set; } protected bool validate_form { get; set; } - protected bool checked_cookies { get; set; } - protected bool checked_query_string { get; set; } protected bool checked_form { get; set; } private static void ThrowValidationException(string name, string key, string value) @@ -210,13 +200,6 @@ namespace Emby.Server.Implementations.SocketSharp return false; } - public void ValidateInput() - { - validate_cookies = true; - validate_query_string = true; - validate_form = true; - } - private bool IsContentType(string ct, bool starts_with) { if (ct == null || ContentType == null) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 53dce667ba..bddccf68ba 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -48,8 +48,6 @@ namespace Emby.Server.Implementations.SocketSharp public string AbsoluteUri => request.GetDisplayUrl().TrimEnd('/'); - public string UserHostAddress => request.HttpContext.Connection.RemoteIpAddress.ToString(); - public string XForwardedFor => StringValues.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"].ToString(); @@ -142,19 +140,6 @@ namespace Emby.Server.Implementations.SocketSharp return name; } - internal static bool ContainsNonAsciiChars(string token) - { - for (int i = 0; i < token.Length; ++i) - { - if ((token[i] < 0x20) || (token[i] > 0x7e)) - { - return true; - } - } - - return false; - } - private string NormalizeIp(string ip) { if (!string.IsNullOrWhiteSpace(ip)) @@ -171,8 +156,6 @@ namespace Emby.Server.Implementations.SocketSharp return ip; } - public bool IsSecureConnection => request.IsHttps || XForwardedProtocol == "https"; - public string[] AcceptTypes => request.Headers.GetCommaSeparatedValues(HeaderNames.Accept); private Dictionary items; diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index c7dccdb6fd..0fd4ea37b1 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -61,11 +61,6 @@ namespace MediaBrowser.Model.Services string AbsoluteUri { get; } - /// - /// The Remote Ip as reported by Request.UserHostAddress - /// - string UserHostAddress { get; } - /// /// The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress /// @@ -76,11 +71,6 @@ namespace MediaBrowser.Model.Services /// string Authorization { get; } - /// - /// e.g. is https or not - /// - bool IsSecureConnection { get; } - string[] AcceptTypes { get; } string PathInfo { get; } From 333bd2107a05c656b8fe27364dcd05fc7ca249d5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 12:40:18 +0100 Subject: [PATCH 054/166] Remove HttpUtility --- Emby.Dlna/PlayTo/PlayToController.cs | 22 +- .../HttpServer/HttpListenerHost.cs | 3 +- MediaBrowser.Model/Services/HttpUtility.cs | 691 ------------------ 3 files changed, 14 insertions(+), 702 deletions(-) delete mode 100644 MediaBrowser.Model/Services/HttpUtility.cs diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index be86dde16a..be6810d0e9 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -19,7 +19,9 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Services; using MediaBrowser.Model.Session; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna.PlayTo { @@ -847,13 +849,13 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - QueryParamCollection values = MyHttpUtility.ParseQueryString(query); + var values = QueryHelpers.ParseQuery(query); - request.DeviceProfileId = values.Get("DeviceProfileId"); - request.DeviceId = values.Get("DeviceId"); - request.MediaSourceId = values.Get("MediaSourceId"); - request.LiveStreamId = values.Get("LiveStreamId"); - request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase); + request.DeviceProfileId = values["DeviceProfileId"].ToString(); + request.DeviceId = values["DeviceId"].ToString(); + request.MediaSourceId = values["MediaSourceId"].ToString(); + request.LiveStreamId = values["LiveStreamId"].ToString(); + request.IsDirectStream = string.Equals("true", values["Static"].ToString(), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); @@ -867,9 +869,9 @@ namespace Emby.Dlna.PlayTo } } - private static int? GetIntValue(QueryParamCollection values, string name) + private static int? GetIntValue(IReadOnlyDictionary values, string name) { - var value = values.Get(name); + var value = values[name].ToString(); if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { @@ -879,9 +881,9 @@ namespace Emby.Dlna.PlayTo return null; } - private static long GetLongValue(QueryParamCollection values, string name) + private static long GetLongValue(IReadOnlyDictionary values, string name) { - var value = values.Get(name); + var value = values[name].ToString(); if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 5b1cb433a9..2abc6c2f4f 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using ServiceStack.Text.Jsv; @@ -298,7 +299,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; diff --git a/MediaBrowser.Model/Services/HttpUtility.cs b/MediaBrowser.Model/Services/HttpUtility.cs deleted file mode 100644 index be180334c6..0000000000 --- a/MediaBrowser.Model/Services/HttpUtility.cs +++ /dev/null @@ -1,691 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; - -namespace MediaBrowser.Model.Services -{ - public static class MyHttpUtility - { - // Must be sorted - static readonly long[] entities = new long[] { - (long)'A' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'A' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'A' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'A' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'A' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'B' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'C' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'C' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'D' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'D' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'E' << 56 | (long)'T' << 48 | (long)'H' << 40, - (long)'E' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'E' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'E' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'E' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'E' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'G' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'I' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'I' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'I' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'I' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'K' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'L' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'M' << 56 | (long)'u' << 48, - (long)'N' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'N' << 56 | (long)'u' << 48, - (long)'O' << 56 | (long)'E' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'O' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'O' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'O' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'O' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'O' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'O' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'P' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'P' << 56 | (long)'i' << 48, - (long)'P' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'P' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'R' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'S' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'S' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'T' << 56 | (long)'H' << 48 | (long)'O' << 40 | (long)'R' << 32 | (long)'N' << 24, - (long)'T' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'T' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'U' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'U' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'U' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'U' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'X' << 56 | (long)'i' << 48, - (long)'Y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'Y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'Z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'a' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'a' << 56 | (long)'c' << 48 | (long)'u' << 40 | (long)'t' << 32 | (long)'e' << 24, - (long)'a' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'l' << 48 | (long)'e' << 40 | (long)'f' << 32 | (long)'s' << 24 | (long)'y' << 16 | (long)'m' << 8, - (long)'a' << 56 | (long)'l' << 48 | (long)'p' << 40 | (long)'h' << 32 | (long)'a' << 24, - (long)'a' << 56 | (long)'m' << 48 | (long)'p' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'d' << 40, - (long)'a' << 56 | (long)'n' << 48 | (long)'g' << 40, - (long)'a' << 56 | (long)'p' << 48 | (long)'o' << 40 | (long)'s' << 32, - (long)'a' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'g' << 24, - (long)'a' << 56 | (long)'s' << 48 | (long)'y' << 40 | (long)'m' << 32 | (long)'p' << 24, - (long)'a' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'a' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'b' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'b' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'b' << 56 | (long)'r' << 48 | (long)'v' << 40 | (long)'b' << 32 | (long)'a' << 24 | (long)'r' << 16, - (long)'b' << 56 | (long)'u' << 48 | (long)'l' << 40 | (long)'l' << 32, - (long)'c' << 56 | (long)'a' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'d' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'c' << 56 | (long)'e' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'c' << 56 | (long)'e' << 48 | (long)'n' << 40 | (long)'t' << 32, - (long)'c' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'c' << 56 | (long)'i' << 48 | (long)'r' << 40 | (long)'c' << 32, - (long)'c' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'b' << 32 | (long)'s' << 24, - (long)'c' << 56 | (long)'o' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'c' << 56 | (long)'o' << 48 | (long)'p' << 40 | (long)'y' << 32, - (long)'c' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'r' << 24, - (long)'c' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'c' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'n' << 16, - (long)'d' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'a' << 48 | (long)'g' << 40 | (long)'g' << 32 | (long)'e' << 24 | (long)'r' << 16, - (long)'d' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'d' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'d' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'a' << 40 | (long)'m' << 32 | (long)'s' << 24, - (long)'d' << 56 | (long)'i' << 48 | (long)'v' << 40 | (long)'i' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'e' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'e' << 56 | (long)'m' << 48 | (long)'p' << 40 | (long)'t' << 32 | (long)'y' << 24, - (long)'e' << 56 | (long)'m' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'n' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'e' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'e' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'i' << 32 | (long)'v' << 24, - (long)'e' << 56 | (long)'t' << 48 | (long)'a' << 40, - (long)'e' << 56 | (long)'t' << 48 | (long)'h' << 40, - (long)'e' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'e' << 56 | (long)'u' << 48 | (long)'r' << 40 | (long)'o' << 32, - (long)'e' << 56 | (long)'x' << 48 | (long)'i' << 40 | (long)'s' << 32 | (long)'t' << 24, - (long)'f' << 56 | (long)'n' << 48 | (long)'o' << 40 | (long)'f' << 32, - (long)'f' << 56 | (long)'o' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'l' << 24 | (long)'l' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'2' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'1' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'c' << 32 | (long)'3' << 24 | (long)'4' << 16, - (long)'f' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'l' << 24, - (long)'g' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'g' << 56 | (long)'e' << 48, - (long)'g' << 56 | (long)'t' << 48, - (long)'h' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'h' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'t' << 24 | (long)'s' << 16, - (long)'h' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'l' << 32 | (long)'i' << 24 | (long)'p' << 16, - (long)'i' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'i' << 56 | (long)'e' << 48 | (long)'x' << 40 | (long)'c' << 32 | (long)'l' << 24, - (long)'i' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'i' << 56 | (long)'m' << 48 | (long)'a' << 40 | (long)'g' << 32 | (long)'e' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'f' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'i' << 56 | (long)'n' << 48 | (long)'t' << 40, - (long)'i' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'i' << 56 | (long)'q' << 48 | (long)'u' << 40 | (long)'e' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'i' << 56 | (long)'s' << 48 | (long)'i' << 40 | (long)'n' << 32, - (long)'i' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'k' << 56 | (long)'a' << 48 | (long)'p' << 40 | (long)'p' << 32 | (long)'a' << 24, - (long)'l' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'m' << 40 | (long)'b' << 32 | (long)'d' << 24 | (long)'a' << 16, - (long)'l' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'l' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'l' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'l' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'e' << 48, - (long)'l' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'w' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'t' << 16, - (long)'l' << 56 | (long)'o' << 48 | (long)'z' << 40, - (long)'l' << 56 | (long)'r' << 48 | (long)'m' << 40, - (long)'l' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'l' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'l' << 56 | (long)'t' << 48, - (long)'m' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'r' << 32, - (long)'m' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'c' << 40 | (long)'r' << 32 | (long)'o' << 24, - (long)'m' << 56 | (long)'i' << 48 | (long)'d' << 40 | (long)'d' << 32 | (long)'o' << 24 | (long)'t' << 16, - (long)'m' << 56 | (long)'i' << 48 | (long)'n' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'m' << 56 | (long)'u' << 48, - (long)'n' << 56 | (long)'a' << 48 | (long)'b' << 40 | (long)'l' << 32 | (long)'a' << 24, - (long)'n' << 56 | (long)'b' << 48 | (long)'s' << 40 | (long)'p' << 32, - (long)'n' << 56 | (long)'d' << 48 | (long)'a' << 40 | (long)'s' << 32 | (long)'h' << 24, - (long)'n' << 56 | (long)'e' << 48, - (long)'n' << 56 | (long)'i' << 48, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40, - (long)'n' << 56 | (long)'o' << 48 | (long)'t' << 40 | (long)'i' << 32 | (long)'n' << 24, - (long)'n' << 56 | (long)'s' << 48 | (long)'u' << 40 | (long)'b' << 32, - (long)'n' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'n' << 56 | (long)'u' << 48, - (long)'o' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'o' << 56 | (long)'e' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'o' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'l' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'e' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'e' << 40 | (long)'g' << 32 | (long)'a' << 24, - (long)'o' << 56 | (long)'m' << 48 | (long)'i' << 40 | (long)'c' << 32 | (long)'r' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'o' << 56 | (long)'p' << 48 | (long)'l' << 40 | (long)'u' << 32 | (long)'s' << 24, - (long)'o' << 56 | (long)'r' << 48, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'f' << 32, - (long)'o' << 56 | (long)'r' << 48 | (long)'d' << 40 | (long)'m' << 32, - (long)'o' << 56 | (long)'s' << 48 | (long)'l' << 40 | (long)'a' << 32 | (long)'s' << 24 | (long)'h' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'l' << 32 | (long)'d' << 24 | (long)'e' << 16, - (long)'o' << 56 | (long)'t' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'o' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'a' << 32, - (long)'p' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'t' << 32, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'m' << 32 | (long)'i' << 24 | (long)'l' << 16, - (long)'p' << 56 | (long)'e' << 48 | (long)'r' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'h' << 48 | (long)'i' << 40, - (long)'p' << 56 | (long)'i' << 48, - (long)'p' << 56 | (long)'i' << 48 | (long)'v' << 40, - (long)'p' << 56 | (long)'l' << 48 | (long)'u' << 40 | (long)'s' << 32 | (long)'m' << 24 | (long)'n' << 16, - (long)'p' << 56 | (long)'o' << 48 | (long)'u' << 40 | (long)'n' << 32 | (long)'d' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'i' << 40 | (long)'m' << 32 | (long)'e' << 24, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'d' << 32, - (long)'p' << 56 | (long)'r' << 48 | (long)'o' << 40 | (long)'p' << 32, - (long)'p' << 56 | (long)'s' << 48 | (long)'i' << 40, - (long)'q' << 56 | (long)'u' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'r' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'d' << 40 | (long)'i' << 32 | (long)'c' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'n' << 40 | (long)'g' << 32, - (long)'r' << 56 | (long)'a' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'r' << 56 | (long)'c' << 48 | (long)'e' << 40 | (long)'i' << 32 | (long)'l' << 24, - (long)'r' << 56 | (long)'d' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'r' << 56 | (long)'e' << 48 | (long)'a' << 40 | (long)'l' << 32, - (long)'r' << 56 | (long)'e' << 48 | (long)'g' << 40, - (long)'r' << 56 | (long)'f' << 48 | (long)'l' << 40 | (long)'o' << 32 | (long)'o' << 24 | (long)'r' << 16, - (long)'r' << 56 | (long)'h' << 48 | (long)'o' << 40, - (long)'r' << 56 | (long)'l' << 48 | (long)'m' << 40, - (long)'r' << 56 | (long)'s' << 48 | (long)'a' << 40 | (long)'q' << 32 | (long)'u' << 24 | (long)'o' << 16, - (long)'r' << 56 | (long)'s' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'b' << 48 | (long)'q' << 40 | (long)'u' << 32 | (long)'o' << 24, - (long)'s' << 56 | (long)'c' << 48 | (long)'a' << 40 | (long)'r' << 32 | (long)'o' << 24 | (long)'n' << 16, - (long)'s' << 56 | (long)'d' << 48 | (long)'o' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'e' << 48 | (long)'c' << 40 | (long)'t' << 32, - (long)'s' << 56 | (long)'h' << 48 | (long)'y' << 40, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24, - (long)'s' << 56 | (long)'i' << 48 | (long)'g' << 40 | (long)'m' << 32 | (long)'a' << 24 | (long)'f' << 16, - (long)'s' << 56 | (long)'i' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'p' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24 | (long)'s' << 16, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'b' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'m' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'1' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'2' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'3' << 32, - (long)'s' << 56 | (long)'u' << 48 | (long)'p' << 40 | (long)'e' << 32, - (long)'s' << 56 | (long)'z' << 48 | (long)'l' << 40 | (long)'i' << 32 | (long)'g' << 24, - (long)'t' << 56 | (long)'a' << 48 | (long)'u' << 40, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'r' << 32 | (long)'e' << 24 | (long)'4' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24, - (long)'t' << 56 | (long)'h' << 48 | (long)'e' << 40 | (long)'t' << 32 | (long)'a' << 24 | (long)'s' << 16 | (long)'y' << 8 | (long)'m' << 0, - (long)'t' << 56 | (long)'h' << 48 | (long)'i' << 40 | (long)'n' << 32 | (long)'s' << 24 | (long)'p' << 16, - (long)'t' << 56 | (long)'h' << 48 | (long)'o' << 40 | (long)'r' << 32 | (long)'n' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'l' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'t' << 56 | (long)'i' << 48 | (long)'m' << 40 | (long)'e' << 32 | (long)'s' << 24, - (long)'t' << 56 | (long)'r' << 48 | (long)'a' << 40 | (long)'d' << 32 | (long)'e' << 24, - (long)'u' << 56 | (long)'A' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'a' << 48 | (long)'r' << 40 | (long)'r' << 32, - (long)'u' << 56 | (long)'c' << 48 | (long)'i' << 40 | (long)'r' << 32 | (long)'c' << 24, - (long)'u' << 56 | (long)'g' << 48 | (long)'r' << 40 | (long)'a' << 32 | (long)'v' << 24 | (long)'e' << 16, - (long)'u' << 56 | (long)'m' << 48 | (long)'l' << 40, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'h' << 24, - (long)'u' << 56 | (long)'p' << 48 | (long)'s' << 40 | (long)'i' << 32 | (long)'l' << 24 | (long)'o' << 16 | (long)'n' << 8, - (long)'u' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'w' << 56 | (long)'e' << 48 | (long)'i' << 40 | (long)'e' << 32 | (long)'r' << 24 | (long)'p' << 16, - (long)'x' << 56 | (long)'i' << 48, - (long)'y' << 56 | (long)'a' << 48 | (long)'c' << 40 | (long)'u' << 32 | (long)'t' << 24 | (long)'e' << 16, - (long)'y' << 56 | (long)'e' << 48 | (long)'n' << 40, - (long)'y' << 56 | (long)'u' << 48 | (long)'m' << 40 | (long)'l' << 32, - (long)'z' << 56 | (long)'e' << 48 | (long)'t' << 40 | (long)'a' << 32, - (long)'z' << 56 | (long)'w' << 48 | (long)'j' << 40, - (long)'z' << 56 | (long)'w' << 48 | (long)'n' << 40 | (long)'j' << 32 - }; - - static readonly char[] entities_values = new char[] { - '\u00C6', '\u00C1', '\u00C2', '\u00C0', '\u0391', '\u00C5', '\u00C3', '\u00C4', '\u0392', '\u00C7', '\u03A7', - '\u2021', '\u0394', '\u00D0', '\u00C9', '\u00CA', '\u00C8', '\u0395', '\u0397', '\u00CB', '\u0393', '\u00CD', - '\u00CE', '\u00CC', '\u0399', '\u00CF', '\u039A', '\u039B', '\u039C', '\u00D1', '\u039D', '\u0152', '\u00D3', - '\u00D4', '\u00D2', '\u03A9', '\u039F', '\u00D8', '\u00D5', '\u00D6', '\u03A6', '\u03A0', '\u2033', '\u03A8', - '\u03A1', '\u0160', '\u03A3', '\u00DE', '\u03A4', '\u0398', '\u00DA', '\u00DB', '\u00D9', '\u03A5', '\u00DC', - '\u039E', '\u00DD', '\u0178', '\u0396', '\u00E1', '\u00E2', '\u00B4', '\u00E6', '\u00E0', '\u2135', '\u03B1', - '\u0026', '\u2227', '\u2220', '\u0027', '\u00E5', '\u2248', '\u00E3', '\u00E4', '\u201E', '\u03B2', '\u00A6', - '\u2022', '\u2229', '\u00E7', '\u00B8', '\u00A2', '\u03C7', '\u02C6', '\u2663', '\u2245', '\u00A9', '\u21B5', - '\u222A', '\u00A4', '\u21D3', '\u2020', '\u2193', '\u00B0', '\u03B4', '\u2666', '\u00F7', '\u00E9', '\u00EA', - '\u00E8', '\u2205', '\u2003', '\u2002', '\u03B5', '\u2261', '\u03B7', '\u00F0', '\u00EB', '\u20AC', '\u2203', - '\u0192', '\u2200', '\u00BD', '\u00BC', '\u00BE', '\u2044', '\u03B3', '\u2265', '\u003E', '\u21D4', '\u2194', - '\u2665', '\u2026', '\u00ED', '\u00EE', '\u00A1', '\u00EC', '\u2111', '\u221E', '\u222B', '\u03B9', '\u00BF', - '\u2208', '\u00EF', '\u03BA', '\u21D0', '\u03BB', '\u2329', '\u00AB', '\u2190', '\u2308', '\u201C', '\u2264', - '\u230A', '\u2217', '\u25CA', '\u200E', '\u2039', '\u2018', '\u003C', '\u00AF', '\u2014', '\u00B5', '\u00B7', - '\u2212', '\u03BC', '\u2207', '\u00A0', '\u2013', '\u2260', '\u220B', '\u00AC', '\u2209', '\u2284', '\u00F1', - '\u03BD', '\u00F3', '\u00F4', '\u0153', '\u00F2', '\u203E', '\u03C9', '\u03BF', '\u2295', '\u2228', '\u00AA', - '\u00BA', '\u00F8', '\u00F5', '\u2297', '\u00F6', '\u00B6', '\u2202', '\u2030', '\u22A5', '\u03C6', '\u03C0', - '\u03D6', '\u00B1', '\u00A3', '\u2032', '\u220F', '\u221D', '\u03C8', '\u0022', '\u21D2', '\u221A', '\u232A', - '\u00BB', '\u2192', '\u2309', '\u201D', '\u211C', '\u00AE', '\u230B', '\u03C1', '\u200F', '\u203A', '\u2019', - '\u201A', '\u0161', '\u22C5', '\u00A7', '\u00AD', '\u03C3', '\u03C2', '\u223C', '\u2660', '\u2282', '\u2286', - '\u2211', '\u2283', '\u00B9', '\u00B2', '\u00B3', '\u2287', '\u00DF', '\u03C4', '\u2234', '\u03B8', '\u03D1', - '\u2009', '\u00FE', '\u02DC', '\u00D7', '\u2122', '\u21D1', '\u00FA', '\u2191', '\u00FB', '\u00F9', '\u00A8', - '\u03D2', '\u03C5', '\u00FC', '\u2118', '\u03BE', '\u00FD', '\u00A5', '\u00FF', '\u03B6', '\u200D', '\u200C' - }; - - #region Methods - - static void WriteCharBytes(IList buf, char ch, Encoding e) - { - if (ch > 255) - { - foreach (byte b in e.GetBytes(new char[] { ch })) - buf.Add(b); - } - else - buf.Add((byte)ch); - } - - public static string UrlDecode(string s, Encoding e) - { - if (null == s) - return null; - - if (s.IndexOf('%') == -1 && s.IndexOf('+') == -1) - return s; - - if (e == null) - e = Encoding.UTF8; - - long len = s.Length; - var bytes = new List(); - int xchar; - char ch; - - for (int i = 0; i < len; i++) - { - ch = s[i]; - if (ch == '%' && i + 2 < len && s[i + 1] != '%') - { - if (s[i + 1] == 'u' && i + 5 < len) - { - // unicode hex sequence - xchar = GetChar(s, i + 2, 4); - if (xchar != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 5; - } - else - WriteCharBytes(bytes, '%', e); - } - else if ((xchar = GetChar(s, i + 1, 2)) != -1) - { - WriteCharBytes(bytes, (char)xchar, e); - i += 2; - } - else - { - WriteCharBytes(bytes, '%', e); - } - continue; - } - - if (ch == '+') - WriteCharBytes(bytes, ' ', e); - else - WriteCharBytes(bytes, ch, e); - } - - byte[] buf = bytes.ToArray(); - bytes = null; - return e.GetString(buf, 0, buf.Length); - - } - - static int GetInt(byte b) - { - char c = (char)b; - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -1; - } - - static int GetChar(string str, int offset, int length) - { - int val = 0; - int end = length + offset; - for (int i = offset; i < end; i++) - { - char c = str[i]; - if (c > 127) - return -1; - - int current = GetInt((byte)c); - if (current == -1) - return -1; - val = (val << 4) + current; - } - - return val; - } - - static bool TryConvertKeyToEntity(string key, out char value) - { - var token = CalculateKeyValue(key); - if (token == 0) - { - value = '\0'; - return false; - } - - var idx = Array.BinarySearch(entities, token); - if (idx < 0) - { - value = '\0'; - return false; - } - - value = entities_values[idx]; - return true; - } - - static long CalculateKeyValue(string s) - { - if (s.Length > 8) - return 0; - - long key = 0; - for (int i = 0; i < s.Length; ++i) - { - long ch = s[i]; - if (ch > 'z' || ch < '0') - return 0; - - key |= ch << ((7 - i) * 8); - } - - return key; - } - - /// - /// Decodes an HTML-encoded string and returns the decoded string. - /// - /// The HTML string to decode. - /// The decoded text. - public static string HtmlDecode(string s) - { - if (s == null) - throw new ArgumentNullException(nameof(s)); - - if (s.IndexOf('&') == -1) - return s; - - var entity = new StringBuilder(); - var output = new StringBuilder(); - int len = s.Length; - // 0 -> nothing, - // 1 -> right after '&' - // 2 -> between '&' and ';' but no '#' - // 3 -> '#' found after '&' and getting numbers - int state = 0; - int number = 0; - int digit_start = 0; - bool hex_number = false; - - for (int i = 0; i < len; i++) - { - char c = s[i]; - if (state == 0) - { - if (c == '&') - { - entity.Append(c); - state = 1; - } - else - { - output.Append(c); - } - continue; - } - - if (c == '&') - { - state = 1; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - output.Append(entity.ToString()); - entity.Length = 0; - entity.Append('&'); - continue; - } - - switch (state) - { - case 1: - if (c == ';') - { - state = 0; - output.Append(entity.ToString()); - output.Append(c); - entity.Length = 0; - break; - } - - number = 0; - hex_number = false; - if (c != '#') - { - state = 2; - } - else - { - state = 3; - } - entity.Append(c); - - break; - case 2: - entity.Append(c); - if (c == ';') - { - string key = entity.ToString(); - state = 0; - entity.Length = 0; - - if (key.Length > 1) - { - var skey = key.Substring(1, key.Length - 2); - if (TryConvertKeyToEntity(skey, out c)) - { - output.Append(c); - break; - } - } - - output.Append(key); - } - - break; - case 3: - if (c == ';') - { - if (number < 0x10000) - { - output.Append((char)number); - } - else - { - output.Append((char)(0xd800 + ((number - 0x10000) >> 10))); - output.Append((char)(0xdc00 + ((number - 0x10000) & 0x3ff))); - } - state = 0; - entity.Length = 0; - digit_start = 0; - break; - } - - if (c == 'x' || c == 'X' && !hex_number) - { - digit_start = i; - hex_number = true; - break; - } - - if (char.IsDigit(c)) - { - if (digit_start == 0) - digit_start = i; - - number = number * (hex_number ? 16 : 10) + ((int)c - '0'); - break; - } - - if (hex_number) - { - if (c >= 'a' && c <= 'f') - { - number = number * 16 + 10 + ((int)c - 'a'); - break; - } - if (c >= 'A' && c <= 'F') - { - number = number * 16 + 10 + ((int)c - 'A'); - break; - } - } - - state = 2; - if (digit_start > 0) - { - entity.Append(s, digit_start, i - digit_start); - digit_start = 0; - } - - entity.Append(c); - break; - } - } - - if (entity.Length > 0) - { - output.Append(entity); - } - else if (digit_start > 0) - { - output.Append(s, digit_start, s.Length - digit_start); - } - return output.ToString(); - } - - public static QueryParamCollection ParseQueryString(string query) - { - return ParseQueryString(query, Encoding.UTF8); - } - - public static QueryParamCollection ParseQueryString(string query, Encoding encoding) - { - if (query == null) - throw new ArgumentNullException(nameof(query)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); - if (query.Length == 0 || (query.Length == 1 && query[0] == '?')) - return new QueryParamCollection(); - if (query[0] == '?') - query = query.Substring(1); - - var result = new QueryParamCollection(); - ParseQueryString(query, encoding, result); - return result; - } - - internal static void ParseQueryString(string query, Encoding encoding, QueryParamCollection result) - { - if (query.Length == 0) - return; - - string decoded = HtmlDecode(query); - int decodedLength = decoded.Length; - int namePos = 0; - bool first = true; - while (namePos <= decodedLength) - { - int valuePos = -1, valueEnd = -1; - for (int q = namePos; q < decodedLength; q++) - { - if (valuePos == -1 && decoded[q] == '=') - { - valuePos = q + 1; - } - else if (decoded[q] == '&') - { - valueEnd = q; - break; - } - } - - if (first) - { - first = false; - if (decoded[namePos] == '?') - namePos++; - } - - string name, value; - if (valuePos == -1) - { - name = null; - valuePos = namePos; - } - else - { - name = UrlDecode(decoded.Substring(namePos, valuePos - namePos - 1), encoding); - } - if (valueEnd < 0) - { - namePos = -1; - valueEnd = decoded.Length; - } - else - { - namePos = valueEnd + 1; - } - value = UrlDecode(decoded.Substring(valuePos, valueEnd - valuePos), encoding); - - result.Add(name, value); - if (namePos == -1) - break; - } - } - #endregion // Methods - } -} From 91afaaf8fe43035bb4832da44b6d6741d2815fb5 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 12:45:06 +0100 Subject: [PATCH 055/166] Cleanup in QueryParamCollection --- Emby.Dlna/PlayTo/PlayToController.cs | 2 +- .../Services/QueryParamCollection.cs | 59 +------------------ 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index be6810d0e9..2da8bb8609 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -849,7 +849,7 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - var values = QueryHelpers.ParseQuery(query); + Dictionary values = QueryHelpers.ParseQuery(query); request.DeviceProfileId = values["DeviceProfileId"].ToString(); request.DeviceId = values["DeviceId"].ToString(); diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 09806ed9c2..9f23b2420f 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Model.Services } - public QueryParamCollection(IDictionary headers) + public QueryParamCollection(IHeaderDictionary headers) { foreach (var pair in headers) { @@ -24,38 +24,6 @@ namespace MediaBrowser.Model.Services } } - public QueryParamCollection(Microsoft.AspNetCore.Http.IHeaderDictionary headers) - { - foreach (var pair in headers) - { - Add(pair.Key, pair.Value); - } - } - - // TODO remove this shit - public QueryParamCollection(WebHeaderCollection webHeaderCollection) - { - foreach (var key in webHeaderCollection.AllKeys) - { - foreach (var value in webHeaderCollection.GetValues(key) ?? Array.Empty()) - { - Add(key, value); - } - } - } - - // TODO remove this shit - public QueryParamCollection(NameValueCollection nameValueCollection) - { - foreach (var key in nameValueCollection.AllKeys) - { - foreach (var value in nameValueCollection.GetValues(key) ?? Array.Empty()) - { - Add(key, value); - } - } - } - public QueryParamCollection(IQueryCollection queryCollection) { foreach (var pair in queryCollection) @@ -74,21 +42,6 @@ namespace MediaBrowser.Model.Services return StringComparer.OrdinalIgnoreCase; } - public string GetKey(int index) - { - return this[index].Name; - } - - public string Get(int index) - { - return this[index].Value; - } - - public virtual string[] GetValues(int index) - { - return new[] { Get(index) }; - } - /// /// Adds a new query parameter. /// @@ -129,16 +82,6 @@ namespace MediaBrowser.Model.Services Add(key, value); } - /// - /// Removes all parameters of the given name. - /// - /// The number of parameters that were removed - /// is null. - public virtual int Remove(string name) - { - return RemoveAll(p => p.Name == name); - } - public string Get(string name) { var stringComparison = GetStringComparison(); From 27e7e792b3d95912787c613f849548809d48f6b1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 14:23:39 +0100 Subject: [PATCH 056/166] Replace some usage of QueryParamCollection --- Emby.Dlna/Api/DlnaServerService.cs | 10 +-- .../ConnectionManager/ConnectionManager.cs | 3 +- .../ContentDirectory/ContentDirectory.cs | 2 +- Emby.Dlna/ControlRequest.cs | 5 +- Emby.Dlna/DlnaManager.cs | 17 ++-- Emby.Dlna/IUpnpService.cs | 5 +- .../MediaReceiverRegistrar.cs | 3 +- .../HttpServer/HttpListenerHost.cs | 7 +- .../HttpServer/HttpResultFactory.cs | 19 ++--- .../HttpServer/WebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 3 +- .../Services/ServiceHandler.cs | 2 +- .../Session/SessionWebSocketListener.cs | 3 +- .../SocketSharp/RequestMono.cs | 2 - .../SocketSharp/WebSocketSharpListener.cs | 6 +- .../SocketSharp/WebSocketSharpRequest.cs | 12 +-- .../SocketSharp/WebSocketSharpResponse.cs | 83 +------------------ .../Playback/BaseStreamingService.cs | 6 +- MediaBrowser.Controller/Dlna/IDlnaManager.cs | 5 +- .../Net/IWebSocketConnection.cs | 3 +- .../Net/WebSocketConnectEventArgs.cs | 6 +- MediaBrowser.Model/Services/IHttpRequest.cs | 5 -- MediaBrowser.Model/Services/IHttpResponse.cs | 12 --- MediaBrowser.Model/Services/IRequest.cs | 21 ++--- .../Services/QueryParamCollection.cs | 20 ----- 25 files changed, 66 insertions(+), 197 deletions(-) diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs index 68bf801637..8bf3797f85 100644 --- a/Emby.Dlna/Api/DlnaServerService.cs +++ b/Emby.Dlna/Api/DlnaServerService.cs @@ -136,7 +136,7 @@ namespace Emby.Dlna.Api { var url = Request.AbsoluteUri; var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); - var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress); var cacheLength = TimeSpan.FromDays(1); var cacheKey = Request.RawUrl.GetMD5(); @@ -147,21 +147,21 @@ namespace Emby.Dlna.Api public object Get(GetContentDirectory request) { - var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ContentDirectory.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetMediaReceiverRegistrar request) { - var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary()); + var xml = MediaReceiverRegistrar.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } public object Get(GetConnnectionManager request) { - var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary()); + var xml = ConnectionManager.GetServiceXml(); return _resultFactory.GetResult(Request, xml, XMLContentType); } @@ -193,7 +193,7 @@ namespace Emby.Dlna.Api return service.ProcessControlRequest(new ControlRequest { - Headers = Request.Headers.ToDictionary(), + Headers = Request.Headers, InputXml = requestStream, TargetServerUuId = id, RequestedUrl = Request.AbsoluteUri diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index cc427f2a15..e138b91d6a 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; @@ -24,7 +23,7 @@ namespace Emby.Dlna.ConnectionManager XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ConnectionManagerXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index b0fec90e69..867e6112fd 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -65,7 +65,7 @@ namespace Emby.Dlna.ContentDirectory } } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new ContentDirectoryXmlBuilder().GetXml(); } diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index afd9a0b874..907d437f85 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; using System.IO; +using Microsoft.AspNetCore.Http; namespace Emby.Dlna { public class ControlRequest { - public IDictionary Headers { get; set; } + public IHeaderDictionary Headers { get; set; } public Stream InputXml { get; set; } @@ -15,7 +16,7 @@ namespace Emby.Dlna public ControlRequest() { - Headers = new Dictionary(); + Headers = new HeaderDictionary(); } } } diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f53d274516..770a901521 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -17,7 +17,9 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.IO; using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; namespace Emby.Dlna { @@ -205,16 +207,13 @@ namespace Emby.Dlna } } - public DeviceProfile GetProfile(IDictionary headers) + public DeviceProfile GetProfile(IHeaderDictionary headers) { if (headers == null) { throw new ArgumentNullException(nameof(headers)); } - // Convert to case insensitive - headers = new Dictionary(headers, StringComparer.OrdinalIgnoreCase); - var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification)); if (profile != null) @@ -230,12 +229,12 @@ namespace Emby.Dlna return profile; } - private bool IsMatch(IDictionary headers, DeviceIdentification profileInfo) + private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo) { return profileInfo.Headers.Any(i => IsMatch(headers, i)); } - private bool IsMatch(IDictionary headers, HttpHeaderInfo header) + private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header) { // Handle invalid user setup if (string.IsNullOrEmpty(header.Name)) @@ -243,14 +242,14 @@ namespace Emby.Dlna return false; } - if (headers.TryGetValue(header.Name, out string value)) + if (headers.TryGetValue(header.Name, out StringValues value)) { switch (header.Match) { case HeaderMatchType.Equals: return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase); case HeaderMatchType.Substring: - var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; + var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1; //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch); return isMatch; case HeaderMatchType.Regex: @@ -493,7 +492,7 @@ namespace Emby.Dlna internal string Path { get; set; } } - public string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress) + public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress) { var profile = GetProfile(headers) ?? GetDefaultProfile(); diff --git a/Emby.Dlna/IUpnpService.cs b/Emby.Dlna/IUpnpService.cs index ab8aa46192..ae90e95c79 100644 --- a/Emby.Dlna/IUpnpService.cs +++ b/Emby.Dlna/IUpnpService.cs @@ -1,5 +1,3 @@ -using System.Collections.Generic; - namespace Emby.Dlna { public interface IUpnpService @@ -7,9 +5,8 @@ namespace Emby.Dlna /// /// Gets the content directory XML. /// - /// The headers. /// System.String. - string GetServiceXml(IDictionary headers); + string GetServiceXml(); /// /// Processes the control request. diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 2b84528eab..9c6022b6cb 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -3,6 +3,7 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -19,7 +20,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar XmlReaderSettingsFactory = xmlReaderSettingsFactory; } - public string GetServiceXml(IDictionary headers) + public string GetServiceXml() { return new MediaReceiverRegistrarXmlBuilder().GetXml(); } diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 2abc6c2f4f..70753e5634 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -22,6 +22,7 @@ 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; @@ -154,7 +155,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryParamCollection() + QueryString = e.QueryString ?? new QueryCollection() }; connection.Closed += Connection_Closed; @@ -606,8 +607,8 @@ namespace Emby.Server.Implementations.HttpServer } finally { - httpRes.Close(); - + // TODO + httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 09cdbc3c26..52c8221f68 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; @@ -246,9 +248,9 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"]; + var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); - if (acceptEncoding != null) + if (string.IsNullOrEmpty(acceptEncoding)) { //if (_brotliCompressor != null && acceptEncoding.IndexOf("br", StringComparison.OrdinalIgnoreCase) != -1) // return "br"; @@ -424,12 +426,12 @@ namespace Emby.Server.Implementations.HttpServer /// private object GetCachedResult(IRequest requestContext, IDictionary 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 +532,7 @@ namespace Emby.Server.Implementations.HttpServer options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(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,7 +550,7 @@ 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["Range"]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -609,11 +611,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// Adds the caching responseHeaders. /// diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index e9d0bac74d..2bf460bd11 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. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Initializes a new instance of the class. diff --git a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs index 666f1f6011..e3047d3926 100644 --- a/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs +++ b/Emby.Server.Implementations/Net/WebSocketConnectEventArgs.cs @@ -1,6 +1,7 @@ using System; using System.Net.WebSockets; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.Net { @@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets the web socket. /// diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs index 7e836e22c3..3c8adfc983 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/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 24903f5e8c..a551433ed9 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/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 113f76b10b..f73adc5ff8 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -118,8 +118,6 @@ namespace Emby.Server.Implementations.SocketSharp public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString(); - protected bool validate_cookies { get; set; } - protected bool validate_query_string { get; set; } protected bool validate_form { get; set; } protected bool checked_form { get; set; } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 77469244b9..9f046c3fd0 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -52,12 +52,10 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var queryString = new QueryParamCollection(ctx.Request.Query); - var connectingArgs = new WebSocketConnectingEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, Endpoint = endpoint }; @@ -73,7 +71,7 @@ using Microsoft.Extensions.Logging; WebSocketConnected(new WebSocketConnectEventArgs { Url = url, - QueryString = queryString, + QueryString = ctx.Request.Query, WebSocket = socket, Endpoint = endpoint }); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bddccf68ba..24fd36062f 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -13,7 +13,6 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IResponse = MediaBrowser.Model.Services.IResponse; namespace Emby.Server.Implementations.SocketSharp @@ -21,7 +20,7 @@ namespace Emby.Server.Implementations.SocketSharp public partial class WebSocketSharpRequest : IHttpRequest { private readonly HttpRequest request; - private readonly IHttpResponse response; + private readonly IResponse response; public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) { @@ -34,11 +33,9 @@ namespace Emby.Server.Implementations.SocketSharp public HttpRequest HttpRequest => request; - public object OriginalRequest => request; - public IResponse Response => response; - public IHttpResponse HttpResponse => response; + public IResponse HttpResponse => response; public string OperationName { get; set; } @@ -396,10 +393,9 @@ namespace Emby.Server.Implementations.SocketSharp public string UserAgent => request.Headers[HeaderNames.UserAgent]; - public QueryParamCollection Headers => new QueryParamCollection(request.Headers); + public IHeaderDictionary Headers => request.Headers; - private QueryParamCollection queryString; - public QueryParamCollection QueryString => queryString ?? (queryString = new QueryParamCollection(request.Query)); + public IQueryCollection QueryString => request.Query; public bool IsLocal => string.Equals(request.HttpContext.Connection.LocalIpAddress.ToString(), request.HttpContext.Connection.RemoteIpAddress.ToString()); diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index f9ecb52a55..c4fbaddd3e 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -1,23 +1,18 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; -using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using System.Threading.Tasks; -using Emby.Server.Implementations; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse; using IRequest = MediaBrowser.Model.Services.IRequest; namespace Emby.Server.Implementations.SocketSharp { - public class WebSocketSharpResponse : IHttpResponse + public class WebSocketSharpResponse : IResponse { private readonly ILogger _logger; @@ -51,42 +46,7 @@ namespace Emby.Server.Implementations.SocketSharp set => _response.ContentType = value; } - public QueryParamCollection Headers => new QueryParamCollection(_response.Headers); - - private static string AsHeaderValue(Cookie cookie) - { - DateTime defaultExpires = DateTime.MinValue; - - var path = cookie.Expires == defaultExpires - ? "/" - : cookie.Path ?? "/"; - - var sb = new StringBuilder(); - - sb.Append($"{cookie.Name}={cookie.Value};path={path}"); - - if (cookie.Expires != defaultExpires) - { - sb.Append($";expires={cookie.Expires:R}"); - } - - if (!string.IsNullOrEmpty(cookie.Domain)) - { - sb.Append($";domain={cookie.Domain}"); - } - - if (cookie.Secure) - { - sb.Append(";Secure"); - } - - if (cookie.HttpOnly) - { - sb.Append(";HttpOnly"); - } - - return sb.ToString(); - } + public IHeaderDictionary Headers => _response.Headers; public void AddHeader(string name, string value) { @@ -111,51 +71,14 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public void Close() - { - if (!this.IsClosed) - { - this.IsClosed = true; - - try - { - var response = this._response; - - var outputStream = response.Body; - - // This is needed with compression - outputStream.Flush(); - outputStream.Dispose(); - } - catch (SocketException) - { - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in HttpListenerResponseWrapper"); - } - } - } - public bool IsClosed { get; - private set; - } - - public void SetCookie(Cookie cookie) - { - var cookieStr = AsHeaderValue(cookie); - _response.Headers.Add("Set-Cookie", cookieStr); + set; } public bool SendChunked { get; set; } - public bool KeepAlive { get; set; } - - public void ClearCookies() - { - } const int StreamCopyToBufferSize = 81920; public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index a6be071b84..ae259a4f59 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -609,12 +609,12 @@ namespace MediaBrowser.Api.Playback { foreach (var param in Request.QueryString) { - if (char.IsLower(param.Name[0])) + if (char.IsLower(param.Key[0])) { // This was probably not parsed initially and should be a StreamOptions // TODO: This should be incorporated either in the lower framework for parsing requests // or the generated URL should correctly serialize it - request.StreamOptions[param.Name] = param.Value; + request.StreamOptions[param.Key] = param.Value; } } } @@ -867,7 +867,7 @@ namespace MediaBrowser.Api.Playback private void ApplyDeviceProfileSettings(StreamState state) { - var headers = Request.Headers.ToDictionary(); + var headers = Request.Headers; if (!string.IsNullOrWhiteSpace(state.Request.DeviceProfileId)) { diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs index a6ee7c5050..41a7686a37 100644 --- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs +++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Dlna { @@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Dlna /// /// The headers. /// DeviceProfile. - DeviceProfile GetProfile(IDictionary headers); + DeviceProfile GetProfile(IHeaderDictionary headers); /// /// Gets the default profile. @@ -64,7 +65,7 @@ namespace MediaBrowser.Controller.Dlna /// The server uu identifier. /// The server address. /// System.String. - string GetServerDescriptionXml(IDictionary headers, string serverUuId, string serverAddress); + string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress); /// /// Gets the icon. diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs index a09b2f7a22..566897b31f 100644 --- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs +++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs @@ -4,6 +4,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -35,7 +36,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - QueryParamCollection QueryString { get; set; } + IQueryCollection QueryString { get; set; } /// /// Gets or sets the receive action. diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs index f26b764bb3..107e674216 100644 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs @@ -1,5 +1,7 @@ using System; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Internal; namespace MediaBrowser.Controller.Net { @@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.Net /// Gets or sets the query string. /// /// The query string. - public QueryParamCollection QueryString { get; set; } + public IQueryCollection QueryString { get; set; } /// /// Gets or sets a value indicating whether [allow connection]. /// @@ -31,7 +33,7 @@ namespace MediaBrowser.Controller.Net public WebSocketConnectingEventArgs() { - QueryString = new QueryParamCollection(); + QueryString = new QueryCollection(); AllowConnection = true; } } diff --git a/MediaBrowser.Model/Services/IHttpRequest.cs b/MediaBrowser.Model/Services/IHttpRequest.cs index 579f80c968..50c6076f30 100644 --- a/MediaBrowser.Model/Services/IHttpRequest.cs +++ b/MediaBrowser.Model/Services/IHttpRequest.cs @@ -2,11 +2,6 @@ namespace MediaBrowser.Model.Services { public interface IHttpRequest : IRequest { - /// - /// The HttpResponse - /// - IHttpResponse HttpResponse { get; } - /// /// The HTTP Verb /// diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs index a8b79f3949..b99d12525d 100644 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ b/MediaBrowser.Model/Services/IHttpResponse.cs @@ -4,17 +4,5 @@ namespace MediaBrowser.Model.Services { public interface IHttpResponse : IResponse { - //ICookies Cookies { get; } - - /// - /// Adds a new Set-Cookie instruction to Response - /// - /// - void SetCookie(Cookie cookie); - - /// - /// Removes all pending Set-Cookie instructions - /// - void ClearCookies(); } } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index 0fd4ea37b1..edb5a2509b 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -1,20 +1,15 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.IO; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { public interface IRequest { - /// - /// The underlying ASP.NET or HttpListener HttpRequest - /// - object OriginalRequest { get; } - IResponse Response { get; } /// @@ -51,9 +46,9 @@ namespace MediaBrowser.Model.Services /// Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } - QueryParamCollection QueryString { get; } + IQueryCollection QueryString { get; } Task GetFormData(); @@ -122,21 +117,15 @@ namespace MediaBrowser.Model.Services Stream OutputStream { get; } - /// - /// Signal that this response has been handled and no more processing should be done. - /// When used in a request or response filter, no more filters or processing is done on this request. - /// - void Close(); - /// /// Gets a value indicating whether this instance is closed. /// - bool IsClosed { get; } + bool IsClosed { get; set; } //Add Metadata to Response Dictionary Items { get; } - QueryParamCollection Headers { get; } + IHeaderDictionary Headers { get; } Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 9f23b2420f..4631a3b633 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using System.Linq; -using System.Net; using MediaBrowser.Model.Dto; -using Microsoft.AspNetCore.Http; namespace MediaBrowser.Model.Services { @@ -13,23 +10,6 @@ namespace MediaBrowser.Model.Services { public QueryParamCollection() { - - } - - public QueryParamCollection(IHeaderDictionary headers) - { - foreach (var pair in headers) - { - Add(pair.Key, pair.Value); - } - } - - public QueryParamCollection(IQueryCollection queryCollection) - { - foreach (var pair in queryCollection) - { - Add(pair.Key, pair.Value); - } } private static StringComparison GetStringComparison() From 1e97e4d462b13baace363be5304bec70940a81a1 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 14:29:44 +0100 Subject: [PATCH 057/166] Remove IHttpResponse --- MediaBrowser.Model/Services/IHttpResponse.cs | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 MediaBrowser.Model/Services/IHttpResponse.cs diff --git a/MediaBrowser.Model/Services/IHttpResponse.cs b/MediaBrowser.Model/Services/IHttpResponse.cs deleted file mode 100644 index b99d12525d..0000000000 --- a/MediaBrowser.Model/Services/IHttpResponse.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Net; - -namespace MediaBrowser.Model.Services -{ - public interface IHttpResponse : IResponse - { - } -} From 647adc51c8681113afbc2c45b7637cc2c50f11f0 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 19:55:25 +0100 Subject: [PATCH 058/166] Fix query log --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 70753e5634..fd46c00578 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? string.Format("{0}?{1}", pagePathWithoutQueryString, newQueryString) + ? $"{pagePathWithoutQueryString}?{newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value))}" : pagePathWithoutQueryString; } From c0b95dbc791acae1c28bf50fb3a33c06935e7192 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 20:11:40 +0100 Subject: [PATCH 059/166] Fix query log for real --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index fd46c00578..dbfb5e2438 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value))}" + ? $"{pagePathWithoutQueryString}?{string.Join("&", newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value)))}" : pagePathWithoutQueryString; } From e47d12198556abfbfb57d2c1a9e45f6f7230bf63 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 21:40:47 +0100 Subject: [PATCH 060/166] Fix websockets --- .../SocketSharp/SharpWebSocket.cs | 5 +++++ .../SocketSharp/WebSocketSharpListener.cs | 13 +++++++++---- .../Net/BasePeriodicWebSocketListener.cs | 13 ++----------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index dcbfb80487..eebbe8b780 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -85,6 +85,11 @@ namespace Emby.Server.Implementations.SocketSharp if (dispose) { _cancellationTokenSource.Cancel(); + if (_webSocket.State == WebSocketState.Open) + { + _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", + CancellationToken.None); + } } _disposed = true; diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 9f046c3fd0..c5abad5d33 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -76,18 +76,23 @@ using Microsoft.Extensions.Logging; Endpoint = endpoint }); - var buffer = WebSocket.CreateClientBuffer(4096, 4096); WebSocketReceiveResult result; var message = new List(); do { + var buffer = WebSocket.CreateServerBuffer(4096); result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); - socket.OnReceiveBytes(buffer.Array); message.AddRange(buffer.Array.Take(result.Count)); - } while (!result.EndOfMessage && result.MessageType != WebSocketMessageType.Close); - socket.OnReceiveBytes(message.ToArray()); + if (result.EndOfMessage) + { + socket.OnReceiveBytes(message.ToArray()); + message.Clear(); + } + } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); + + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, result.CloseStatusDescription, _disposeCancellationToken); socket.Dispose(); diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 4242a00e21..d2d0fbbc6c 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -80,12 +80,7 @@ namespace MediaBrowser.Controller.Net protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected virtual bool SendOnTimer => false; - - protected virtual void ParseMessageParams(string[] values) - { - - } + protected bool SendOnTimer => false; /// /// Starts sending messages over a web socket @@ -98,11 +93,6 @@ namespace MediaBrowser.Controller.Net var dueTimeMs = long.Parse(vals[0], UsCulture); var periodMs = long.Parse(vals[1], UsCulture); - if (vals.Length > 2) - { - ParseMessageParams(vals.Skip(2).ToArray()); - } - var cancellationTokenSource = new CancellationTokenSource(); Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); @@ -250,6 +240,7 @@ namespace MediaBrowser.Controller.Net { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); + connection.Item1.Dispose(); var timer = connection.Item3; if (timer != null) From dab8e150520f5a76d875a926530632783e17006f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 21:48:28 +0100 Subject: [PATCH 061/166] Check websocket state before closing --- .../SocketSharp/WebSocketSharpListener.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index c5abad5d33..9422673f52 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -93,8 +93,12 @@ using Microsoft.Extensions.Logging; } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, - result.CloseStatusDescription, _disposeCancellationToken); + if (webSocketContext.State == WebSocketState.Open) + { + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, _disposeCancellationToken); + } + socket.Dispose(); } else From 71ed8409446774c395b339728e2436c3b30536d8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 22:09:22 +0100 Subject: [PATCH 062/166] Simplify websocket listeners --- .../ScheduledTasksWebSocketListener.cs | 3 +- .../Session/SessionInfoWebSocketListener.cs | 3 +- .../System/ActivityLogWebSocketListener.cs | 3 +- .../Net/BasePeriodicWebSocketListener.cs | 74 +++---------------- 4 files changed, 13 insertions(+), 70 deletions(-) diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs index b0c4b29c17..d24a187430 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs @@ -58,9 +58,8 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Gets the data to send. /// - /// The state. /// Task{IEnumerable{TaskInfo}}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) + protected override Task> GetDataToSend() { return Task.FromResult(TaskManager.ScheduledTasks .OrderBy(i => i.Name) diff --git a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs index beb2fb11d0..b79e9f84b1 100644 --- a/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs @@ -79,9 +79,8 @@ namespace MediaBrowser.Api.Session /// /// Gets the data to send. /// - /// The state. /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken cancellationToken) + protected override Task> GetDataToSend() { return Task.FromResult(_sessionManager.Sessions); } diff --git a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs index 43f3c5a223..a036619b81 100644 --- a/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs +++ b/MediaBrowser.Api/System/ActivityLogWebSocketListener.cs @@ -38,9 +38,8 @@ namespace MediaBrowser.Api.System /// /// Gets the data to send. /// - /// The state. /// Task{SystemInfo}. - protected override Task> GetDataToSend(WebSocketListenerState state, CancellationToken CancellationToken) + protected override Task> GetDataToSend() { return Task.FromResult(new List()); } diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index d2d0fbbc6c..5a6a0ea31e 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -22,8 +22,8 @@ namespace MediaBrowser.Controller.Net /// /// The _active connections /// - protected readonly List> ActiveConnections = - new List>(); + protected readonly List> ActiveConnections = + new List>(); /// /// Gets the name. @@ -34,9 +34,8 @@ namespace MediaBrowser.Controller.Net /// /// Gets the data to send. /// - /// The state. /// Task{`1}. - protected abstract Task GetDataToSend(TStateType state, CancellationToken cancellationToken); + protected abstract Task GetDataToSend(); /// /// The logger @@ -80,8 +79,6 @@ namespace MediaBrowser.Controller.Net protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected bool SendOnTimer => false; - /// /// Starts sending messages over a web socket /// @@ -97,10 +94,6 @@ namespace MediaBrowser.Controller.Net Logger.LogDebug("{1} Begin transmitting over websocket to {0}", message.Connection.RemoteEndPoint, GetType().Name); - var timer = SendOnTimer ? - new Timer(TimerCallback, message.Connection, Timeout.Infinite, Timeout.Infinite) : - null; - var state = new TStateType { IntervalMs = periodMs, @@ -109,47 +102,13 @@ namespace MediaBrowser.Controller.Net lock (ActiveConnections) { - ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, timer, state)); + ActiveConnections.Add(new Tuple(message.Connection, cancellationTokenSource, state)); } - - if (timer != null) - { - timer.Change(TimeSpan.FromMilliseconds(dueTimeMs), TimeSpan.FromMilliseconds(periodMs)); - } - } - - /// - /// Timers the callback. - /// - /// The state. - private void TimerCallback(object state) - { - var connection = (IWebSocketConnection)state; - - Tuple tuple; - - lock (ActiveConnections) - { - tuple = ActiveConnections.FirstOrDefault(c => c.Item1 == connection); - } - - if (tuple == null) - { - return; - } - - if (connection.State != WebSocketState.Open || tuple.Item2.IsCancellationRequested) - { - DisposeConnection(tuple); - return; - } - - SendData(tuple); } protected void SendData(bool force) { - Tuple[] tuples; + Tuple[] tuples; lock (ActiveConnections) { @@ -158,7 +117,7 @@ namespace MediaBrowser.Controller.Net { if (c.Item1.State == WebSocketState.Open && !c.Item2.IsCancellationRequested) { - var state = c.Item4; + var state = c.Item3; if (force || (DateTime.UtcNow - state.DateLastSendUtc).TotalMilliseconds >= state.IntervalMs) { @@ -177,17 +136,17 @@ namespace MediaBrowser.Controller.Net } } - private async void SendData(Tuple tuple) + private async void SendData(Tuple tuple) { var connection = tuple.Item1; try { - var state = tuple.Item4; + var state = tuple.Item3; var cancellationToken = tuple.Item2.Token; - var data = await GetDataToSend(state, cancellationToken).ConfigureAwait(false); + var data = await GetDataToSend().ConfigureAwait(false); if (data != null) { @@ -236,24 +195,11 @@ namespace MediaBrowser.Controller.Net /// Disposes the connection. /// /// The connection. - private void DisposeConnection(Tuple connection) + private void DisposeConnection(Tuple connection) { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); connection.Item1.Dispose(); - var timer = connection.Item3; - - if (timer != null) - { - try - { - timer.Dispose(); - } - catch (ObjectDisposedException) - { - //TODO Investigate and properly fix. - } - } try { From fb1de5a9213f7da98ed15a6975201d6bca3537d4 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:22:55 +0100 Subject: [PATCH 063/166] Remove more cruft and add the beginnings of a socket middleware --- .../ApplicationHost.cs | 2 + .../HttpServer/WebSocketConnection.cs | 36 +--------- .../Middleware/WebSocketMiddleware.cs | 36 ++++++++++ Emby.Server.Implementations/Net/IWebSocket.cs | 5 -- .../SocketSharp/WebSocketSharpListener.cs | 65 +++++++------------ .../WebSocket/WebSocketManager.cs | 7 ++ .../WebSockets/WebSocketManager.cs | 22 +++++++ .../MediaBrowser.Controller.csproj | 8 ++- 8 files changed, 97 insertions(+), 84 deletions(-) create mode 100644 Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs create mode 100644 Emby.Server.Implementations/WebSocket/WebSocketManager.cs create mode 100644 Emby.Server.Implementations/WebSockets/WebSocketManager.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 0b4a2fd30e..e558b43544 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -35,6 +35,7 @@ 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; @@ -641,6 +642,7 @@ namespace Emby.Server.Implementations app.UseWebSockets(); app.UseResponseCompression(); + // TODO app.UseMiddleware(); app.Use(ExecuteWebsocketHandlerAsync); app.Use(ExecuteHttpHandlerAsync); }) diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs index 2bf460bd11..54a16040f7 100644 --- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs +++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs @@ -102,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; @@ -143,34 +137,6 @@ namespace Emby.Server.Implementations.HttpServer } } - /// - /// Called when [receive]. - /// - /// The memory block. - /// The length of the memory block. - private void OnReceiveInternal(Memory 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; @@ -194,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/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs new file mode 100644 index 0000000000..a1d0e77d65 --- /dev/null +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -0,0 +1,36 @@ +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 _logger; + private readonly WebSocketManager _webSocketManager; + + public WebSocketMiddleware(RequestDelegate next, ILogger 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); + _webSocketManager.AddSocket(webSocketContext); + } + else + { + await _next.Invoke(httpContext); + } + } + } +} diff --git a/Emby.Server.Implementations/Net/IWebSocket.cs b/Emby.Server.Implementations/Net/IWebSocket.cs index 4671de07c5..4d160aa66f 100644 --- a/Emby.Server.Implementations/Net/IWebSocket.cs +++ b/Emby.Server.Implementations/Net/IWebSocket.cs @@ -45,9 +45,4 @@ namespace Emby.Server.Implementations.Net /// Task. Task SendAsync(string text, bool endOfMessage, CancellationToken cancellationToken); } - - public interface IMemoryWebSocket - { - Action, int> OnReceiveMemoryBytes { get; set; } - } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 9422673f52..5ddd316477 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -33,8 +33,6 @@ using Microsoft.Extensions.Logging; public Func ErrorHandler { get; set; } public Func RequestHandler { get; set; } - public Action WebSocketConnecting { get; set; } - public Action WebSocketConnected { get; set; } private static void LogRequest(ILogger logger, HttpRequest request) @@ -52,60 +50,41 @@ using Microsoft.Extensions.Logging; var endpoint = ctx.Connection.RemoteIpAddress.ToString(); var url = ctx.Request.GetDisplayUrl(); - var connectingArgs = new WebSocketConnectingEventArgs + 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 - }; + }); - WebSocketConnecting?.Invoke(connectingArgs); + WebSocketReceiveResult result; + var message = new List(); - if (connectingArgs.AllowConnection) + do { - _logger.LogDebug("Web socket connection allowed"); + var buffer = WebSocket.CreateServerBuffer(4096); + result = await webSocketContext.ReceiveAsync(buffer, _disposeCancellationToken); + message.AddRange(buffer.Array.Take(result.Count)); - var webSocketContext = await ctx.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - var socket = new SharpWebSocket(webSocketContext, _logger); - - WebSocketConnected(new WebSocketConnectEventArgs + if (result.EndOfMessage) { - Url = url, - QueryString = ctx.Request.Query, - WebSocket = socket, - Endpoint = endpoint - }); - - WebSocketReceiveResult result; - var message = new List(); - - 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.OnReceiveBytes(message.ToArray()); + message.Clear(); } + } while (socket.State == WebSocketState.Open && result.MessageType != WebSocketMessageType.Close); - socket.Dispose(); - } - else + + if (webSocketContext.State == WebSocketState.Open) { - _logger.LogWarning("Web socket connection not allowed"); - ctx.Response.StatusCode = 401; + await webSocketContext.CloseAsync(result.CloseStatus ?? WebSocketCloseStatus.NormalClosure, + result.CloseStatusDescription, _disposeCancellationToken); } + + socket.Dispose(); } catch (Exception ex) { diff --git a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs new file mode 100644 index 0000000000..7472820cf2 --- /dev/null +++ b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs @@ -0,0 +1,7 @@ +namespace Emby.Server.Implementations.WebSocket +{ + public class WebSocketManager + { + + } +} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs new file mode 100644 index 0000000000..7e74a45277 --- /dev/null +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Concurrent; +using System.Net.WebSockets; + +namespace Emby.Server.Implementations.WebSockets +{ + public class WebSocketManager + { + private readonly ConcurrentDictionary _activeWebSockets; + + public WebSocketManager() + { + _activeWebSockets = new ConcurrentDictionary(); + } + + public void AddSocket(WebSocket webSocket) + { + var guid = Guid.NewGuid(); + _activeWebSockets.TryAdd(guid, webSocket); + } + } +} diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 01893f1b55..81e255d525 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -16,6 +16,12 @@ + + + ..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll + + + netstandard2.0 false From 588a13377c347cbd56462c64b80f0b9a563e534f Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:24:43 +0100 Subject: [PATCH 064/166] Remove a file that shouldn't have been added --- Emby.Server.Implementations/WebSocket/WebSocketManager.cs | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 Emby.Server.Implementations/WebSocket/WebSocketManager.cs diff --git a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs b/Emby.Server.Implementations/WebSocket/WebSocketManager.cs deleted file mode 100644 index 7472820cf2..0000000000 --- a/Emby.Server.Implementations/WebSocket/WebSocketManager.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Emby.Server.Implementations.WebSocket -{ - public class WebSocketManager - { - - } -} From 1ac282b12e20abc91402015369dc17543604f9fb Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 27 Feb 2019 23:39:20 +0100 Subject: [PATCH 065/166] Call SharpWebSocket's Closed event handler before disposing --- Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs index eebbe8b780..62b16ed8c8 100644 --- a/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs +++ b/Emby.Server.Implementations/SocketSharp/SharpWebSocket.cs @@ -90,6 +90,7 @@ namespace Emby.Server.Implementations.SocketSharp _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed by client", CancellationToken.None); } + Closed?.Invoke(this, EventArgs.Empty); } _disposed = true; From edba82db373da8fbab8159d6ab2483052ebab231 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Wed, 27 Feb 2019 23:05:12 -0800 Subject: [PATCH 066/166] fixed logic flip in auth empty check and fixed crypto algo choice --- .../Cryptography/CryptographyProvider.cs | 34 +++++++++++++------ .../Library/DefaultAuthenticationProvider.cs | 2 +- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index ea719309cd..3c9403ba8e 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -4,6 +4,7 @@ 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 @@ -11,16 +12,18 @@ namespace Emby.Server.Implementations.Cryptography public class CryptographyProvider : ICryptoProvider { private HashSet SupportedHashMethods; - public string DefaultHashMethod => "SHA256"; + public string DefaultHashMethod => "PBKDF2"; private RandomNumberGenerator rng; private 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 SupportedHashMethods = new HashSet() { - "MD5" + "MD5" ,"System.Security.Cryptography.MD5" ,"SHA" ,"SHA1" @@ -75,10 +78,15 @@ namespace Emby.Server.Implementations.Cryptography private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) { //downgrading for now as we need this library to be dotnetstandard compliant - using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) + //with this downgrade we'll add a check to make sure we're on the downgrade method at the moment + if(method == DefaultHashMethod) { - return r.GetBytes(32); + 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) @@ -93,18 +101,22 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) { - if (SupportedHashMethods.Contains(HashMethod)) + if(HashMethod == DefaultHashMethod) { - if (salt.Length == 0) + return PBKDF2(HashMethod, bytes, salt, defaultiterations); + } + else if (SupportedHashMethods.Contains(HashMethod)) + { + using (var h = HashAlgorithm.Create(HashMethod)) { - using (var h = HashAlgorithm.Create(HashMethod)) + if (salt.Length == 0) { return h.ComputeHash(bytes); } - } - else - { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); + else + { + return h.ComputeHash(bytes.Concat(salt).ToArray()); + } } } else diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index b58374adb4..7ccdccc0ad 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.Library //but at least they are in the new format. private void ConvertPasswordFormat(User user) { - if (!string.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(user.Password)) { return; } From 331260cf803f7d4500d2b88eabb78be994425081 Mon Sep 17 00:00:00 2001 From: TheBird956 Date: Tue, 26 Feb 2019 03:38:39 +0000 Subject: [PATCH 067/166] Translated using Weblate (French) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr/ --- Emby.Server.Implementations/Localization/Core/fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json index 52afb4e492..e434b7605b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr.json +++ b/Emby.Server.Implementations/Localization/Core/fr.json @@ -44,7 +44,7 @@ "NameInstallFailed": "{0} échec d'installation", "NameSeasonNumber": "Saison {0}", "NameSeasonUnknown": "Saison Inconnue", - "NewVersionIsAvailable": "Une nouvelle version d'Jellyfin Serveur est disponible au téléchargement.", + "NewVersionIsAvailable": "Une nouvelle version de Jellyfin Serveur est disponible au téléchargement.", "NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible", "NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée", "NotificationOptionAudioPlayback": "Lecture audio démarrée", @@ -89,7 +89,7 @@ "UserOnlineFromDevice": "{0} s'est connecté depuis {1}", "UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié", "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", - "UserStartedPlayingItemWithValues": "{0} est entrain de lire {1} sur {2}", + "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre librairie", "ValueSpecialEpisodeName": "Spécial - {0}", From c8788d83fbd921fccf80b7f49be83db2c73a210b Mon Sep 17 00:00:00 2001 From: TheBird956 Date: Tue, 26 Feb 2019 03:23:03 +0000 Subject: [PATCH 068/166] Translated using Weblate (French (Canada)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr_CA/ --- .../Localization/Core/fr-CA.json | 170 +++++++++--------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json index 7202be9f56..4b4db39a8b 100644 --- a/Emby.Server.Implementations/Localization/Core/fr-CA.json +++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json @@ -1,97 +1,97 @@ { "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", + "AppDeviceValues": "Application : {0}, Appareil : {1}", "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", + "Artists": "Artistes", + "AuthenticationSucceededWithUserName": "{0} s'est authentifié avec succès", + "Books": "Livres", + "CameraImageUploadedFrom": "Une nouvelle image de caméra a été téléchargée depuis {0}", + "Channels": "Chaînes", + "ChapterNameValue": "Chapitre {0}", "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", + "DeviceOfflineWithName": "{0} s'est déconnecté", + "DeviceOnlineWithName": "{0} est connecté", + "FailedLoginAttemptWithUserName": "Échec d'une tentative de connexion de {0}", + "Favorites": "Favoris", + "Folders": "Dossiers", "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", + "HeaderAlbumArtists": "Artistes de l'album", + "HeaderCameraUploads": "Photos transférées", "HeaderContinueWatching": "Continuer à regarder", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", + "HeaderFavoriteAlbums": "Albums favoris", + "HeaderFavoriteArtists": "Artistes favoris", + "HeaderFavoriteEpisodes": "Épisodes favoris", + "HeaderFavoriteShows": "Séries favorites", + "HeaderFavoriteSongs": "Chansons favorites", + "HeaderLiveTV": "TV en direct", "HeaderNextUp": "À Suivre", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", + "HeaderRecordingGroups": "Groupes d'enregistrements", + "HomeVideos": "Vidéos personnelles", + "Inherit": "Hériter", + "ItemAddedWithName": "{0} a été ajouté à la médiathèque", + "ItemRemovedWithName": "{0} a été supprimé de la médiathèque", + "LabelIpAddressValue": "Adresse IP : {0}", + "LabelRunningTimeValue": "Durée : {0}", + "Latest": "Derniers", + "MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour", + "MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers la version {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour", + "MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour", + "MixedContent": "Contenu mixte", + "Movies": "Films", + "Music": "Musique", + "MusicVideos": "Vidéos musicales", + "NameInstallFailed": "{0} échec d'installation", + "NameSeasonNumber": "Saison {0}", + "NameSeasonUnknown": "Saison Inconnue", + "NewVersionIsAvailable": "Une nouvelle version du serveur Jellyfin est disponible au téléchargement.", + "NotificationOptionApplicationUpdateAvailable": "Mise à jour de l'application disponible", + "NotificationOptionApplicationUpdateInstalled": "Mise à jour de l'application installée", + "NotificationOptionAudioPlayback": "Lecture audio démarrée", + "NotificationOptionAudioPlaybackStopped": "Lecture audio arrêtée", + "NotificationOptionCameraImageUploaded": "L'image de l'appareil photo a été transférée", + "NotificationOptionInstallationFailed": "Échec d'installation", + "NotificationOptionNewLibraryContent": "Nouveau contenu ajouté", + "NotificationOptionPluginError": "Erreur d'extension", + "NotificationOptionPluginInstalled": "Extension installée", + "NotificationOptionPluginUninstalled": "Extension désinstallée", + "NotificationOptionPluginUpdateInstalled": "Mise à jour d'extension installée", + "NotificationOptionServerRestartRequired": "Un redémarrage du serveur est requis", + "NotificationOptionTaskFailed": "Échec de tâche planifiée", + "NotificationOptionUserLockedOut": "Utilisateur verrouillé", + "NotificationOptionVideoPlayback": "Lecture vidéo démarrée", + "NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée", "Photos": "Photos", - "Playlists": "Playlists", - "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", - "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Playlists": "Listes de lecture", + "Plugin": "Extension", + "PluginInstalledWithName": "{0} a été installé", + "PluginUninstalledWithName": "{0} a été désinstallé", + "PluginUpdatedWithName": "{0} a été mis à jour", + "ProviderValue": "Fournisseur : {0}", + "ScheduledTaskFailedWithName": "{0} a échoué", + "ScheduledTaskStartedWithName": "{0} a commencé", + "ServerNameNeedsToBeRestarted": "{0} doit être redémarré", + "Shows": "Émissions", + "Songs": "Chansons", + "StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", + "SubtitlesDownloadedForItem": "Les sous-titres de {0} ont été téléchargés", + "Sync": "Synchroniser", + "System": "Système", + "TvShows": "Séries Télé", + "User": "Utilisateur", + "UserCreatedWithName": "L'utilisateur {0} a été créé", + "UserDeletedWithName": "L'utilisateur {0} a été supprimé", + "UserDownloadingItemWithValues": "{0} est en train de télécharger {1}", + "UserLockedOutWithName": "L'utilisateur {0} a été verrouillé", + "UserOfflineFromDevice": "{0} s'est déconnecté depuis {1}", + "UserOnlineFromDevice": "{0} s'est connecté depuis {1}", + "UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié", + "UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}", + "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", + "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", + "ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque", "ValueSpecialEpisodeName": "Spécial - {0}", "VersionNumber": "Version {0}" } From 7c13021def4432123917b6cf18c129e19ddff756 Mon Sep 17 00:00:00 2001 From: EffeF Date: Sun, 24 Feb 2019 17:44:06 +0000 Subject: [PATCH 069/166] Translated using Weblate (Italian) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/it/ --- .../Localization/Core/it.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/it.json b/Emby.Server.Implementations/Localization/Core/it.json index a5f1e8f94d..357883cd37 100644 --- a/Emby.Server.Implementations/Localization/Core/it.json +++ b/Emby.Server.Implementations/Localization/Core/it.json @@ -34,17 +34,17 @@ "LabelRunningTimeValue": "Durata: {0}", "Latest": "Più recenti", "MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}", "MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata", "MessageServerConfigurationUpdated": "La configurazione del server è stata aggiornata", "MixedContent": "Contenuto misto", "Movies": "Film", "Music": "Musica", "MusicVideos": "Video musicali", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} installazione fallita", "NameSeasonNumber": "Stagione {0}", "NameSeasonUnknown": "Stagione sconosciuto", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.", "NotificationOptionApplicationUpdateAvailable": "Aggiornamento dell'applicazione disponibile", "NotificationOptionApplicationUpdateInstalled": "Aggiornamento dell'applicazione installato", "NotificationOptionAudioPlayback": "La riproduzione audio è iniziata", @@ -70,12 +70,12 @@ "ProviderValue": "Provider: {0}", "ScheduledTaskFailedWithName": "{0} fallito", "ScheduledTaskStartedWithName": "{0} avviati", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} deve essere riavviato", "Shows": "Programmi", "Songs": "Canzoni", "StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.", "SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}", "SubtitlesDownloadedForItem": "Sottotitoli scaricati per {0}", "Sync": "Sincronizza", "System": "Sistema", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "La politica dell'utente è stata aggiornata per {0}", "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1}", "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueSpecialEpisodeName": "Speciale - {0}", "VersionNumber": "Versione {0}" } From 311caf3fc3e45cc332e922897f963b95cb60f98a Mon Sep 17 00:00:00 2001 From: v1tin Date: Mon, 18 Feb 2019 19:02:01 +0000 Subject: [PATCH 070/166] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/ --- .../Localization/Core/pt-BR.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json index aaedf08505..dbc9c4c4b8 100644 --- a/Emby.Server.Implementations/Localization/Core/pt-BR.json +++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json @@ -5,7 +5,7 @@ "Artists": "Artistas", "AuthenticationSucceededWithUserName": "{0} autenticado com sucesso", "Books": "Livros", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", + "CameraImageUploadedFrom": "Uma nova imagem da câmera foi submetida de {0}", "Channels": "Canais", "ChapterNameValue": "Capítulo {0}", "Collections": "Coletâneas", @@ -30,21 +30,21 @@ "Inherit": "Herdar", "ItemAddedWithName": "{0} foi adicionado à biblioteca", "ItemRemovedWithName": "{0} foi removido da biblioteca", - "LabelIpAddressValue": "Endereço ip: {0}", + "LabelIpAddressValue": "Endereço IP: {0}", "LabelRunningTimeValue": "Tempo de execução: {0}", "Latest": "Recente", "MessageApplicationUpdated": "O servidor Jellyfin foi atualizado", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "O Servidor Jellyfin foi atualizado para {0}", "MessageNamedServerConfigurationUpdatedWithValue": "A seção {0} da configuração do servidor foi atualizada", "MessageServerConfigurationUpdated": "A configuração do servidor foi atualizada", "MixedContent": "Conteúdo misto", "Movies": "Filmes", "Music": "Música", "MusicVideos": "Vídeos musicais", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "A instalação de {0} falhou", "NameSeasonNumber": "Temporada {0}", "NameSeasonUnknown": "Temporada Desconhecida", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "NewVersionIsAvailable": "Uma nova versão do servidor Jellyfin está disponível para download.", "NotificationOptionApplicationUpdateAvailable": "Atualização de aplicativo disponível", "NotificationOptionApplicationUpdateInstalled": "Atualização de aplicativo instalada", "NotificationOptionAudioPlayback": "Reprodução de áudio iniciada", @@ -70,12 +70,12 @@ "ProviderValue": "Provedor: {0}", "ScheduledTaskFailedWithName": "{0} falhou", "ScheduledTaskStartedWithName": "{0} iniciada", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "O servidor {0} precisa ser reiniciado", "Shows": "Séries", "Songs": "Músicas", "StartupEmbyServerIsLoading": "O Servidor Jellyfin está carregando. Por favor tente novamente em breve.", "SubtitleDownloadFailureForItem": "Download de legendas falhou para {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", + "SubtitleDownloadFailureFromForItem": "Houve um problema ao baixar as legendas de {0} para {1}", "SubtitlesDownloadedForItem": "Legendas baixadas para {0}", "Sync": "Sincronizar", "System": "Sistema", @@ -91,7 +91,7 @@ "UserPolicyUpdatedWithName": "A política de usuário foi atualizada para {0}", "UserStartedPlayingItemWithValues": "{0} iniciou a reprodução de {1}", "UserStoppedPlayingItemWithValues": "{0} parou de reproduzir {1}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", + "ValueHasBeenAddedToLibrary": "{0} foi adicionado a sua biblioteca", "ValueSpecialEpisodeName": "Especial - {0}", "VersionNumber": "Versão {0}" } From d0e4e0f6004644b58cd5f64a89f82467759e5876 Mon Sep 17 00:00:00 2001 From: SaddFox Date: Tue, 26 Feb 2019 23:05:46 +0000 Subject: [PATCH 071/166] Translated using Weblate (Slovenian) Currently translated at 87.2% (82 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sl/ --- .../Localization/Core/sl-SI.json | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/sl-SI.json b/Emby.Server.Implementations/Localization/Core/sl-SI.json index e850257d4a..b50ff4706e 100644 --- a/Emby.Server.Implementations/Localization/Core/sl-SI.json +++ b/Emby.Server.Implementations/Localization/Core/sl-SI.json @@ -1,62 +1,62 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", + "Albums": "Albumi", + "AppDeviceValues": "Aplikacija: {0}, Naprava: {1}", + "Application": "Aplikacija", + "Artists": "Izvajalci", + "AuthenticationSucceededWithUserName": "{0} preverjanje uspešno", + "Books": "Knjige", + "CameraImageUploadedFrom": "Nova fotografija je bila naložena z {0}", + "Channels": "Kanali", + "ChapterNameValue": "Poglavje {0}", + "Collections": "Zbirke", "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "DeviceOnlineWithName": "{0} je povezan", + "FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}", + "Favorites": "Priljubljeni", + "Folders": "Mape", + "Genres": "Zvrsti", + "HeaderAlbumArtists": "Izvajalci albuma", + "HeaderCameraUploads": "Posnetki kamere", + "HeaderContinueWatching": "Nadaljuj gledanje", + "HeaderFavoriteAlbums": "Priljubljeni albumi", + "HeaderFavoriteArtists": "Priljubljeni izvajalci", + "HeaderFavoriteEpisodes": "Priljubljene epizode", + "HeaderFavoriteShows": "Priljubljene serije", + "HeaderFavoriteSongs": "Priljubljene pesmi", + "HeaderLiveTV": "TV v živo", + "HeaderNextUp": "Sledi", + "HeaderRecordingGroups": "Zbirke posnetkov", + "HomeVideos": "Domači posnetki", + "Inherit": "Podeduj", + "ItemAddedWithName": "{0} je dodan v knjižnico", + "ItemRemovedWithName": "{0} je bil odstranjen iz knjižnice", + "LabelIpAddressValue": "IP naslov: {0}", + "LabelRunningTimeValue": "Čas trajanja: {0}", + "Latest": "Najnovejše", + "MessageApplicationUpdated": "Jellyfin strežnik je bil posodobljen", + "MessageApplicationUpdatedTo": "Jellyfin strežnik je bil posodobljen na {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", + "MessageServerConfigurationUpdated": "Nastavitve strežnika so bile posodobljene", + "MixedContent": "Razne vsebine", + "Movies": "Filmi", + "Music": "Glasba", + "MusicVideos": "Glasbeni posnetki", + "NameInstallFailed": "{0} namestitev neuspešna", + "NameSeasonNumber": "Sezona {0}", + "NameSeasonUnknown": "Season neznana", + "NewVersionIsAvailable": "Nova razničica Jellyfin strežnika je na voljo za prenos.", + "NotificationOptionApplicationUpdateAvailable": "Posodobitev aplikacije je na voljo", + "NotificationOptionApplicationUpdateInstalled": "Posodobitev aplikacije je bila nameščena", + "NotificationOptionAudioPlayback": "Predvajanje zvoka začeto", + "NotificationOptionAudioPlaybackStopped": "Predvajanje zvoka zaustavljeno", + "NotificationOptionCameraImageUploaded": "Posnetek kamere naložen", + "NotificationOptionInstallationFailed": "Napaka pri nameščanju", + "NotificationOptionNewLibraryContent": "Nove vsebine dodane", + "NotificationOptionPluginError": "Napaka dodatka", + "NotificationOptionPluginInstalled": "Dodatek nameščen", + "NotificationOptionPluginUninstalled": "Dodatek odstranjen", + "NotificationOptionPluginUpdateInstalled": "Posodobitev dodatka nameščena", + "NotificationOptionServerRestartRequired": "Potreben je ponovni zagon strežnika", "NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionUserLockedOut": "User locked out", "NotificationOptionVideoPlayback": "Video playback started", From 53ed6e5e6efb69572a61cb57a31b600f4ba5c02b Mon Sep 17 00:00:00 2001 From: ElFantasma Date: Fri, 22 Feb 2019 18:49:18 +0000 Subject: [PATCH 072/166] Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_AR/ --- .../Localization/Core/es-AR.json | 184 +++++++++--------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json index c01bb0c501..dc73ba6b34 100644 --- a/Emby.Server.Implementations/Localization/Core/es-AR.json +++ b/Emby.Server.Implementations/Localization/Core/es-AR.json @@ -1,97 +1,97 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", - "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", - "ChapterNameValue": "Chapter {0}", - "Collections": "Collections", - "DeviceOfflineWithName": "{0} has disconnected", - "DeviceOnlineWithName": "{0} is connected", - "FailedLoginAttemptWithUserName": "Failed login attempt from {0}", - "Favorites": "Favorites", - "Folders": "Folders", - "Genres": "Genres", - "HeaderAlbumArtists": "Album Artists", - "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", - "HeaderFavoriteArtists": "Favorite Artists", - "HeaderFavoriteEpisodes": "Favorite Episodes", - "HeaderFavoriteShows": "Favorite Shows", - "HeaderFavoriteSongs": "Favorite Songs", - "HeaderLiveTV": "Live TV", - "HeaderNextUp": "Next Up", - "HeaderRecordingGroups": "Recording Groups", - "HomeVideos": "Home videos", - "Inherit": "Inherit", - "ItemAddedWithName": "{0} was added to the library", - "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", - "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", - "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", - "MessageServerConfigurationUpdated": "Server configuration has been updated", - "MixedContent": "Mixed content", - "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", - "NotificationOptionApplicationUpdateAvailable": "Application update available", - "NotificationOptionApplicationUpdateInstalled": "Application update installed", - "NotificationOptionAudioPlayback": "Audio playback started", - "NotificationOptionAudioPlaybackStopped": "Audio playback stopped", - "NotificationOptionCameraImageUploaded": "Camera image uploaded", - "NotificationOptionInstallationFailed": "Installation failure", - "NotificationOptionNewLibraryContent": "New content added", - "NotificationOptionPluginError": "Plugin failure", - "NotificationOptionPluginInstalled": "Plugin installed", - "NotificationOptionPluginUninstalled": "Plugin uninstalled", - "NotificationOptionPluginUpdateInstalled": "Plugin update installed", - "NotificationOptionServerRestartRequired": "Server restart required", - "NotificationOptionTaskFailed": "Scheduled task failure", - "NotificationOptionUserLockedOut": "User locked out", - "NotificationOptionVideoPlayback": "Video playback started", - "NotificationOptionVideoPlaybackStopped": "Video playback stopped", - "Photos": "Photos", - "Playlists": "Playlists", + "Albums": "Álbumes", + "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", + "Application": "Aplicación", + "Artists": "Artistas", + "AuthenticationSucceededWithUserName": "{0} autenticado correctamente", + "Books": "Libros", + "CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}", + "Channels": "Canales", + "ChapterNameValue": "Capítulo {0}", + "Collections": "Colecciones", + "DeviceOfflineWithName": "{0} se ha desconectado", + "DeviceOnlineWithName": "{0} está conectado", + "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}", + "Favorites": "Favoritos", + "Folders": "Carpetas", + "Genres": "Géneros", + "HeaderAlbumArtists": "Artistas de álbumes", + "HeaderCameraUploads": "Subidas de cámara", + "HeaderContinueWatching": "Continuar viendo", + "HeaderFavoriteAlbums": "Álbumes favoritos", + "HeaderFavoriteArtists": "Artistas favoritos", + "HeaderFavoriteEpisodes": "Episodios favoritos", + "HeaderFavoriteShows": "Programas favoritos", + "HeaderFavoriteSongs": "Canciones favoritas", + "HeaderLiveTV": "TV en vivo", + "HeaderNextUp": "Continuar Viendo", + "HeaderRecordingGroups": "Grupos de grabación", + "HomeVideos": "Videos caseros", + "Inherit": "Heredar", + "ItemAddedWithName": "{0} se ha añadido a la biblioteca", + "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", + "LabelIpAddressValue": "Dirección IP: {0}", + "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", + "Latest": "Últimos", + "MessageApplicationUpdated": "El servidor Jellyfin fue actualizado", + "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", + "MessageNamedServerConfigurationUpdatedWithValue": "Fue actualizada la sección {0} de la configuración del servidor", + "MessageServerConfigurationUpdated": "Fue actualizada la configuración del servidor", + "MixedContent": "Contenido mixto", + "Movies": "Películas", + "Music": "Música", + "MusicVideos": "Videos musicales", + "NameInstallFailed": "{0} error de instalación", + "NameSeasonNumber": "Temporada {0}", + "NameSeasonUnknown": "Temporada desconocida", + "NewVersionIsAvailable": "Disponible una nueva versión de Jellyfin para descargar.", + "NotificationOptionApplicationUpdateAvailable": "Actualización de la aplicación disponible", + "NotificationOptionApplicationUpdateInstalled": "Actualización de la aplicación instalada", + "NotificationOptionAudioPlayback": "Se inició la reproducción de audio", + "NotificationOptionAudioPlaybackStopped": "Se detuvo la reproducción de audio", + "NotificationOptionCameraImageUploaded": "Imagen de la cámara cargada", + "NotificationOptionInstallationFailed": "Error de instalación", + "NotificationOptionNewLibraryContent": "Nuevo contenido añadido", + "NotificationOptionPluginError": "Error en plugin", + "NotificationOptionPluginInstalled": "Plugin instalado", + "NotificationOptionPluginUninstalled": "Plugin desinstalado", + "NotificationOptionPluginUpdateInstalled": "Actualización del complemento instalada", + "NotificationOptionServerRestartRequired": "Se requiere reinicio del servidor", + "NotificationOptionTaskFailed": "Error de tarea programada", + "NotificationOptionUserLockedOut": "Usuario bloqueado", + "NotificationOptionVideoPlayback": "Se inició la reproducción de video", + "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", + "Photos": "Fotos", + "Playlists": "Listas de reproducción", "Plugin": "Plugin", - "PluginInstalledWithName": "{0} was installed", - "PluginUninstalledWithName": "{0} was uninstalled", - "PluginUpdatedWithName": "{0} was updated", - "ProviderValue": "Provider: {0}", - "ScheduledTaskFailedWithName": "{0} failed", - "ScheduledTaskStartedWithName": "{0} started", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "PluginInstalledWithName": "{0} fue instalado", + "PluginUninstalledWithName": "{0} fue desinstalado", + "PluginUpdatedWithName": "{0} fue actualizado", + "ProviderValue": "Proveedor: {0}", + "ScheduledTaskFailedWithName": "{0} falló", + "ScheduledTaskStartedWithName": "{0} iniciada", + "ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado", "Shows": "Series", - "Songs": "Songs", - "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", + "Songs": "Canciones", + "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", - "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", - "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}", - "Sync": "Sync", - "System": "System", - "TvShows": "TV Shows", - "User": "User", - "UserCreatedWithName": "User {0} has been created", - "UserDeletedWithName": "User {0} has been deleted", - "UserDownloadingItemWithValues": "{0} is downloading {1}", - "UserLockedOutWithName": "User {0} has been locked out", - "UserOfflineFromDevice": "{0} has disconnected from {1}", - "UserOnlineFromDevice": "{0} is online from {1}", - "UserPasswordChangedWithName": "Password has been changed for user {0}", - "UserPolicyUpdatedWithName": "User policy has been updated for {0}", - "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", - "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", - "ValueHasBeenAddedToLibrary": "{0} has been added to your media library", - "ValueSpecialEpisodeName": "Special - {0}", - "VersionNumber": "Version {0}" + "SubtitleDownloadFailureFromForItem": "Fallo de descarga de subtítulos desde {0} para {1}", + "SubtitlesDownloadedForItem": "Descargar subtítulos para {0}", + "Sync": "Sincronizar", + "System": "Sistema", + "TvShows": "Series de TV", + "User": "Usuario", + "UserCreatedWithName": "El usuario {0} ha sido creado", + "UserDeletedWithName": "El usuario {0} ha sido borrado", + "UserDownloadingItemWithValues": "{0} está descargando {1}", + "UserLockedOutWithName": "El usuario {0} ha sido bloqueado", + "UserOfflineFromDevice": "{0} se ha desconectado de {1}", + "UserOnlineFromDevice": "{0} está en línea desde {1}", + "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", + "UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}", + "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", + "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", + "ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia", + "ValueSpecialEpisodeName": "Especial - {0}", + "VersionNumber": "Versión {0}" } From 3d3d879b99776e6d156773aec5693ee7c63479ea Mon Sep 17 00:00:00 2001 From: Deniz Date: Fri, 22 Feb 2019 08:15:26 +0000 Subject: [PATCH 073/166] Translated using Weblate (Turkish) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/ --- .../Localization/Core/tr.json | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/tr.json b/Emby.Server.Implementations/Localization/Core/tr.json index 495f82db6d..9e00eba62f 100644 --- a/Emby.Server.Implementations/Localization/Core/tr.json +++ b/Emby.Server.Implementations/Localization/Core/tr.json @@ -1,12 +1,12 @@ { - "Albums": "Albums", - "AppDeviceValues": "App: {0}, Device: {1}", - "Application": "Application", - "Artists": "Artists", - "AuthenticationSucceededWithUserName": "{0} successfully authenticated", - "Books": "Books", + "Albums": "Albümler", + "AppDeviceValues": "Uygulama: {0}, Aygıt: {1}", + "Application": "Uygulama", + "Artists": "Sanatçılar", + "AuthenticationSucceededWithUserName": "{0} başarı ile giriş yaptı", + "Books": "Kitaplar", "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}", - "Channels": "Channels", + "Channels": "Kanallar", "ChapterNameValue": "Chapter {0}", "Collections": "Collections", "DeviceOfflineWithName": "{0} has disconnected", @@ -17,8 +17,8 @@ "Genres": "Genres", "HeaderAlbumArtists": "Album Artists", "HeaderCameraUploads": "Camera Uploads", - "HeaderContinueWatching": "Continue Watching", - "HeaderFavoriteAlbums": "Favorite Albums", + "HeaderContinueWatching": "İzlemeye Devam Et", + "HeaderFavoriteAlbums": "Favori Albümler", "HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteEpisodes": "Favorite Episodes", "HeaderFavoriteShows": "Favori Showlar", @@ -30,21 +30,21 @@ "Inherit": "Inherit", "ItemAddedWithName": "{0} was added to the library", "ItemRemovedWithName": "{0} was removed from the library", - "LabelIpAddressValue": "Ip address: {0}", - "LabelRunningTimeValue": "Running time: {0}", + "LabelIpAddressValue": "Ip adresi: {0}", + "LabelRunningTimeValue": "Çalışma süresi: {0}", "Latest": "Latest", - "MessageApplicationUpdated": "Jellyfin Server has been updated", + "MessageApplicationUpdated": "Jellyfin Sunucusu güncellendi", "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated", "MessageServerConfigurationUpdated": "Server configuration has been updated", "MixedContent": "Mixed content", "Movies": "Movies", - "Music": "Music", - "MusicVideos": "Music videos", - "NameInstallFailed": "{0} installation failed", - "NameSeasonNumber": "Season {0}", - "NameSeasonUnknown": "Season Unknown", - "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", + "Music": "Müzik", + "MusicVideos": "Müzik videoları", + "NameInstallFailed": "{0} kurulum başarısız", + "NameSeasonNumber": "Sezon {0}", + "NameSeasonUnknown": "Bilinmeyen Sezon", + "NewVersionIsAvailable": "Jellyfin Sunucusunun yeni bir versiyonu indirmek için hazır.", "NotificationOptionApplicationUpdateAvailable": "Application update available", "NotificationOptionApplicationUpdateInstalled": "Application update installed", "NotificationOptionAudioPlayback": "Audio playback started", From 6bdb5debd2492b71d11f9628889b8c29b6321a77 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 1 Mar 2019 14:08:51 +0100 Subject: [PATCH 074/166] Add some websocket manager boilerplate --- .../Middleware/WebSocketMiddleware.cs | 5 +- .../WebSockets/WebSocketHandler.cs | 10 +++ .../WebSockets/WebSocketManager.cs | 90 +++++++++++++++++-- 3 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 Emby.Server.Implementations/WebSockets/WebSocketHandler.cs diff --git a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs index a1d0e77d65..268bf4042d 100644 --- a/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs +++ b/Emby.Server.Implementations/Middleware/WebSocketMiddleware.cs @@ -25,7 +25,10 @@ namespace Emby.Server.Implementations.Middleware if (httpContext.WebSockets.IsWebSocketRequest) { var webSocketContext = await httpContext.WebSockets.AcceptWebSocketAsync(null).ConfigureAwait(false); - _webSocketManager.AddSocket(webSocketContext); + if (webSocketContext != null) + { + await _webSocketManager.OnWebSocketConnected(webSocketContext); + } } else { diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs new file mode 100644 index 0000000000..70b9e85aa9 --- /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 message); + } +} diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 7e74a45277..888f2f0fce 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -1,22 +1,100 @@ 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 ConcurrentDictionary _activeWebSockets; + private readonly IWebSocketHandler[] _webSocketHandlers; + private readonly IJsonSerializer _jsonSerializer; + private readonly ILogger _logger; + private const int BufferSize = 4096; - public WebSocketManager() + public WebSocketManager(IWebSocketHandler[] webSocketHandlers, IJsonSerializer jsonSerializer, ILogger logger) { - _activeWebSockets = new ConcurrentDictionary(); + _webSocketHandlers = webSocketHandlers; + _jsonSerializer = jsonSerializer; + _logger = logger; } - public void AddSocket(WebSocket webSocket) + public async Task OnWebSocketConnected(WebSocket webSocket) { - var guid = Guid.NewGuid(); - _activeWebSockets.TryAdd(guid, webSocket); + var taskCompletionSource = new TaskCompletionSource(); + var cancellationToken = new CancellationTokenSource().Token; + WebSocketReceiveResult result; + var message = new List(); + + 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); + } + } + + public async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource 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 json + 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>(message); + + _logger.LogDebug("Websocket message received: {0}", info.MessageType); + + var tasks = _webSocketHandlers.Select(handler => Task.Run(() => + { + try + { + handler.ProcessMessage(info).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.LogError(ex, "{0} failed processing WebSocket message {1}", handler.GetType().Name, info.MessageType ?? string.Empty); + } + })); + + await Task.WhenAll(tasks); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error processing web socket message"); + } } } } From ed07ed44ae99d3c53a95774cdbd6254c9cb0a76e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 1 Mar 2019 19:30:48 +0100 Subject: [PATCH 075/166] Simplify rating loading --- .../Localization/LocalizationManager.cs | 153 +++--------------- .../Localization/Ratings/au.csv | 8 + .../Localization/Ratings/be.csv | 6 + .../Localization/Ratings/de.csv | 10 ++ .../Localization/Ratings/ru.csv | 5 + 5 files changed, 54 insertions(+), 128 deletions(-) create mode 100644 Emby.Server.Implementations/Localization/Ratings/au.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/be.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/de.csv create mode 100644 Emby.Server.Implementations/Localization/Ratings/ru.csv diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 31217730bf..2c59579a89 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,42 @@ namespace Emby.Server.Implementations.Localization continue; } - string filename = "ratings-" + resource.Substring(ratingsResource.Length); + string countryCode = resource.Substring(ratingsResource.Length, 2); + var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); - if (existingFiles.Contains(filename)) + using (var str = _assembly.GetManifestResourceStream(resource)) + using (var reader = new StreamReader(str)) { - continue; - } - - using (var stream = _assembly.GetManifestResourceStream(resource)) - { - 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("Misformed line in ratings file for country {CountryCode}", countryCode); + } +#endif } } - } + _logger.LogWarning("{t}", countryCode); - 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 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)); - - /// - /// Gets the localization path. - /// - /// The localization path. - public string LocalizationPath - => Path.Combine(_configurationManager.ApplicationPaths.ProgramDataPath, "localization"); - public string NormalizeFormKD(string text) => text.Normalize(NormalizationForm.FormKD); @@ -288,47 +226,6 @@ namespace Emby.Server.Implementations.Localization return value; } - /// - /// Loads the ratings. - /// - /// The file. - /// Dictionary{System.StringParentalRating}. - private async Task LoadRatings(string file) - { - Dictionary dict - = new Dictionary(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" }; /// diff --git a/Emby.Server.Implementations/Localization/Ratings/au.csv b/Emby.Server.Implementations/Localization/Ratings/au.csv new file mode 100644 index 0000000000..940375e268 --- /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 0000000000..d3937caf78 --- /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 0000000000..f944a140d0 --- /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/ru.csv b/Emby.Server.Implementations/Localization/Ratings/ru.csv new file mode 100644 index 0000000000..1bc94affd6 --- /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 From e823c11b46ccf0473aa72cb52cd5a3a9f977e61b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 08:29:23 +0100 Subject: [PATCH 076/166] Add certificate to https and minor cleanup --- .../ApplicationHost.cs | 26 +++++++++---------- .../HttpServer/HttpListenerHost.cs | 11 -------- .../WebSockets/WebSocketHandler.cs | 2 +- .../WebSockets/WebSocketManager.cs | 8 +++--- Jellyfin.Server/CoreAppHost.cs | 2 -- 5 files changed, 18 insertions(+), 31 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index e558b43544..05d8ee410c 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -626,9 +626,12 @@ namespace Emby.Server.Implementations { options.Listen(IPAddress.Any, HttpPort); options.Listen(IPAddress.Loopback, HttpPort); - // TODO certs - options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); - options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(); }); + + if (CertificateInfo != null) + { + options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + } }) .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) .ConfigureServices(services => @@ -927,11 +930,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)) { @@ -1004,7 +1005,7 @@ namespace Emby.Server.Implementations return info; } - protected virtual FFMpegInfo GetFFMpegInfo() + protected FFMpegInfo GetFFMpegInfo() { return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) .GetFFMpegInfo(StartupOptions); @@ -1085,7 +1086,7 @@ namespace Emby.Server.Implementations /// 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"); @@ -1211,15 +1212,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 GetUrlPrefixes() { diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dbfb5e2438..263fcdbe9e 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -855,16 +855,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; - } } } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs index 70b9e85aa9..eb18774408 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketHandler.cs @@ -5,6 +5,6 @@ namespace Emby.Server.Implementations.WebSockets { public interface IWebSocketHandler { - Task ProcessMessage(WebSocketMessage message); + Task ProcessMessage(WebSocketMessage message, TaskCompletionSource taskCompletionSource); } } diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 888f2f0fce..5db2c8da73 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -35,6 +35,7 @@ namespace Emby.Server.Implementations.WebSockets WebSocketReceiveResult result; var message = new List(); + // Keep listening for incoming messages, otherwise the socket closes automatically do { var buffer = WebSocket.CreateServerBuffer(BufferSize); @@ -57,7 +58,7 @@ namespace Emby.Server.Implementations.WebSockets } } - public async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) + private async Task ProcessMessage(byte[] messageBytes, TaskCompletionSource taskCompletionSource) { var charset = CharsetDetector.DetectFromBytes(messageBytes).Detected?.EncodingName; var message = string.Equals(charset, "utf-8", StringComparison.OrdinalIgnoreCase) @@ -81,11 +82,12 @@ namespace Emby.Server.Implementations.WebSockets { try { - handler.ProcessMessage(info).ConfigureAwait(false); + handler.ProcessMessage(info, taskCompletionSource).ConfigureAwait(false); } catch (Exception ex) { - _logger.LogError(ex, "{0} failed processing WebSocket message {1}", handler.GetType().Name, info.MessageType ?? string.Empty); + _logger.LogError(ex, "{HandlerType} failed processing WebSocket message {MessageType}", + handler.GetType().Name, info.MessageType ?? string.Empty); } })); diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 9e52247900..17259c7370 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -34,8 +34,6 @@ namespace Jellyfin.Server public override bool CanSelfRestart => StartupOptions.RestartPath != null; - protected override bool SupportsDualModeSockets => true; - protected override void RestartInternal() => Program.Restart(); protected override IEnumerable GetAssembliesWithPartsInternal() From d450169964680ed249f140de8422bf415b44958c Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 08:46:17 +0100 Subject: [PATCH 077/166] Use EnableHttps instead of CertificateInfo --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 05d8ee410c..284900e870 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -627,7 +627,7 @@ namespace Emby.Server.Implementations options.Listen(IPAddress.Any, HttpPort); options.Listen(IPAddress.Loopback, HttpPort); - if (CertificateInfo != null) + if (EnableHttps) { options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); From 1cc433eabc4813fd175b8ce63b27490663cbbcee Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 13:54:14 +0100 Subject: [PATCH 078/166] Start the webhost before RunStartupTasks and fix ContentEncoding --- .../SocketSharp/WebSocketSharpRequest.cs | 25 ++++++++++++++++--- Jellyfin.Server/Program.cs | 1 - 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index 24fd36062f..976f2ec065 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -408,11 +408,28 @@ namespace Emby.Server.Implementations.SocketSharp public string ContentType => request.ContentType; - private Encoding contentEncoding; - public Encoding ContentEncoding + private Encoding ContentEncoding { - get => contentEncoding ?? Encoding.GetEncoding(request.Headers[HeaderNames.ContentEncoding].ToString()); - set => contentEncoding = value; + 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; diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index bfc50468f7..a43c3fea5c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -144,7 +144,6 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - appHost.Host.Run(); // TODO: read input for a stop command try From 6263b73d9c5659f2a6216dd2bea55df230ae3fcb Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 14:30:41 +0100 Subject: [PATCH 079/166] Await host startup --- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 284900e870..c2e6481af7 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -650,6 +650,8 @@ namespace Emby.Server.Implementations app.Use(ExecuteHttpHandlerAsync); }) .Build(); + + await Host.StartAsync(); } public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) From 040871459b572ce0dad09ae8f27938cb424b98e7 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 3 Mar 2019 14:35:54 +0100 Subject: [PATCH 080/166] Remove some unused references --- .../MediaReceiverRegistrar.cs | 2 -- .../ApplicationHost.cs | 3 -- .../Emby.Server.Implementations.csproj | 4 --- Emby.Server.Implementations/Startup.cs | 34 ------------------- 4 files changed, 43 deletions(-) delete mode 100644 Emby.Server.Implementations/Startup.cs diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 9c6022b6cb..e47143c16a 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -1,9 +1,7 @@ -using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Xml; -using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c2e6481af7..00a5f864df 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -111,13 +111,11 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; -using X509Certificate = System.Security.Cryptography.X509Certificates.X509Certificate; namespace Emby.Server.Implementations { @@ -638,7 +636,6 @@ namespace Emby.Server.Implementations { services.AddResponseCompression(); services.AddHttpContextAccessor(); - services.TryAddSingleton(); }) .Configure(app => { diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index d4db5f2fcc..39e9ed3756 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -26,11 +26,7 @@ - - - - diff --git a/Emby.Server.Implementations/Startup.cs b/Emby.Server.Implementations/Startup.cs deleted file mode 100644 index 164c7eeaa7..0000000000 --- a/Emby.Server.Implementations/Startup.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Linq; -using MediaBrowser.Api; -using MediaBrowser.Controller; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace Emby.Server.Implementations -{ - public class Startup - { - public IConfiguration Configuration { get; } - - public Startup(IConfiguration configuration) => Configuration = configuration; - - // Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddRouting(); - } - - // Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - - } - } - -} From 6cc1bd544add65fdf7758c04655586c206675193 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 19:31:26 +0100 Subject: [PATCH 081/166] Fix a logging statement --- .../SocketSharp/WebSocketSharpListener.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 5ddd316477..2df826957a 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -12,8 +12,9 @@ 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 +namespace Emby.Server.Implementations.SocketSharp { public class WebSocketSharpListener : IHttpListener { @@ -39,7 +40,7 @@ using Microsoft.Extensions.Logging; { var url = request.GetDisplayUrl(); - logger.LogInformation("{0} {1}. UserAgent: {2}", "WS", url, request.Headers["User-Agent"].ToString()); + logger.LogInformation("WS {Url}. UserAgent: {UserAgent}", url, request.Headers[HeaderNames.UserAgent].ToString()); } public async Task ProcessWebSocketRequest(HttpContext ctx) From 557c4d065ddba0ccc6780c6a4c7811fe45351c88 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 19:55:59 +0100 Subject: [PATCH 082/166] Review comments --- .../SocketSharp/WebSocketSharpResponse.cs | 6 +- .../MediaBrowser.Controller.csproj | 6 - MediaBrowser.sln | 374 +++++++++--------- 3 files changed, 188 insertions(+), 198 deletions(-) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index c4fbaddd3e..5964107651 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -71,11 +71,7 @@ namespace Emby.Server.Implementations.SocketSharp public Stream OutputStream => _response.Body; - public bool IsClosed - { - get; - set; - } + public bool IsClosed { get; set; } public bool SendChunked { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 81e255d525..5898d8f984 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -16,12 +16,6 @@ - - - ..\..\..\..\..\usr\local\share\dotnet\sdk\NuGetFallbackFolder\microsoft.aspnetcore.http.extensions\2.2.0\lib\netstandard2.0\Microsoft.AspNetCore.Http.Extensions.dll - - - netstandard2.0 false diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 5edc1a25a6..b1d3fcdda8 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,187 +1,187 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.3 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - SharedVersion.cs = SharedVersion.cs - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} - EndGlobalSection - GlobalSection(AutomaticVersions) = postSolution - UpdateAssemblyVersion = True - UpdateAssemblyFileVersion = True - UpdateAssemblyInfoVersion = True - AssemblyVersionSettings = None.None.None.None - AssemblyFileVersionSettings = None.None.None.None - AssemblyInfoVersionSettings = None.None.None.None - UpdatePackageVersion = False - AssemblyInfoVersionType = SettingsVersion - InheritWinAppVersionFrom = None - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - Policies = $0 - $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n - $0.DotNetNamingPolicy = $2 - $2.DirectoryNamespaceAssociation = PrefixedHierarchical - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", "MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj", "{5624B7B5-B5A7-41D8-9F10-CC5611109619}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Photos", "Emby.Photos\Emby.Photos.csproj", "{89AB4548-770D-41FD-A891-8DAFF44F452C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DvdLib", "DvdLib\DvdLib.csproj", "{713F42B5-878E-499D-A878-E4C652B1D5E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BDInfo", "BDInfo\BDInfo.csproj", "{88AE38DF-19D7-406F-A6A9-09527719A21E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Server.Implementations", "Emby.Server.Implementations\Emby.Server.Implementations.csproj", "{E383961B-9356-4D5D-8233-9A1079D03055}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj", "{21002819-C39A-4D3E-BE83-2A276A77FB1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.XmlTv", "Emby.XmlTv\Emby.XmlTv\Emby.XmlTv.csproj", "{6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IsoMounter", "Emby.IsoMounting\IsoMounter\IsoMounter.csproj", "{9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Server", "Jellyfin.Server\Jellyfin.Server.csproj", "{07E39F42-A2C6-4B32-AF8C-725F957A73FF}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{41093F42-C7CC-4D07-956B-6182CBEDE2EC}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + SharedVersion.cs = SharedVersion.cs + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5624B7B5-B5A7-41D8-9F10-CC5611109619}.Release|Any CPU.Build.0 = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {442B5058-DCAF-4263-BB6A-F21E31120A1B}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4402D4-E910-443B-B8FC-2C18286A2CA0}.Release|Any CPU.Build.0 = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23499896-B135-4527-8574-C26E926EA99E}.Release|Any CPU.Build.0 = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7EF9F3E0-697D-42F3-A08F-19DEB5F84392}.Release|Any CPU.Build.0 = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89AB4548-770D-41FD-A891-8DAFF44F452C}.Release|Any CPU.Build.0 = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {713F42B5-878E-499D-A878-E4C652B1D5E8}.Release|Any CPU.Build.0 = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88AE38DF-19D7-406F-A6A9-09527719A21E}.Release|Any CPU.Build.0 = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E383961B-9356-4D5D-8233-9A1079D03055}.Release|Any CPU.Build.0 = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21002819-C39A-4D3E-BE83-2A276A77FB1F}.Release|Any CPU.Build.0 = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.Build.0 = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6EAFA7F0-8A82-49E6-B2FA-086C5CAEA95B}.Release|Any CPU.Build.0 = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BA471D2-6DB9-4DBF-B3A0-9FB3171F94A6}.Release|Any CPU.Build.0 = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {960295EE-4AF4-4440-A525-B4C295B01A61}.Release|Any CPU.Build.0 = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07E39F42-A2C6-4B32-AF8C-725F957A73FF}.Release|Any CPU.Build.0 = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {154872D9-6C12-4007-96E3-8F70A58386CE}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} + EndGlobalSection + GlobalSection(AutomaticVersions) = postSolution + UpdateAssemblyVersion = True + UpdateAssemblyFileVersion = True + UpdateAssemblyInfoVersion = True + AssemblyVersionSettings = None.None.None.None + AssemblyFileVersionSettings = None.None.None.None + AssemblyInfoVersionSettings = None.None.None.None + UpdatePackageVersion = False + AssemblyInfoVersionType = SettingsVersion + InheritWinAppVersionFrom = None + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.StandardHeader = $1 + $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $0.DotNetNamingPolicy = $2 + $2.DirectoryNamespaceAssociation = PrefixedHierarchical + EndGlobalSection +EndGlobal From 9020f68ce16b8184c2b9f5cefde2b966b15fc741 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 20:08:54 +0100 Subject: [PATCH 083/166] Use QueryHelpers.AddQueryString --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 263fcdbe9e..16e3c383b8 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -321,7 +321,7 @@ namespace Emby.Server.Implementations.HttpServer string pagePathWithoutQueryString = url.Split(new[] { '?' }, StringSplitOptions.RemoveEmptyEntries)[0]; return newQueryString.Count > 0 - ? $"{pagePathWithoutQueryString}?{string.Join("&", newQueryString.Select(value => value.Key + " = " + string.Join(", ", value.Value)))}" + ? QueryHelpers.AddQueryString(pagePathWithoutQueryString, newQueryString.ToDictionary(kv => kv.Key, kv => kv.Value.ToString())) : pagePathWithoutQueryString; } From 0419deeec41cb7021c22a65fc4b63153075fd969 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 4 Mar 2019 20:18:35 +0100 Subject: [PATCH 084/166] Update LocalizationManager.cs --- Emby.Server.Implementations/Localization/LocalizationManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 2c59579a89..d55b258a3d 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -88,7 +88,7 @@ namespace Emby.Server.Implementations.Localization 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 })); + dict.Add(parts[0], new ParentalRating { Name = parts[0], Value = value }); } #if DEBUG else From 0250204f14e8a004c926e1ec263723459788a029 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 4 Mar 2019 22:26:57 +0100 Subject: [PATCH 085/166] Expand todo --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 16e3c383b8..19b1b26e89 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -607,7 +607,7 @@ namespace Emby.Server.Implementations.HttpServer } finally { - // TODO + // TODO response closes automatically after the handler is done, but some functions rely on knowing if it's closed or not httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; From 17ca23d73b9dc440373abcde2df39e1cf7a50e35 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 07:41:41 +0100 Subject: [PATCH 086/166] Don't dispose the connection --- MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index 5a6a0ea31e..8444125462 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -199,7 +199,8 @@ namespace MediaBrowser.Controller.Net { Logger.LogDebug("{1} stop transmitting over websocket to {0}", connection.Item1.RemoteEndPoint, GetType().Name); - connection.Item1.Dispose(); + // TODO disposing the connection seems to break websockets in subtle ways, so what is the purpose of this function really... + // connection.Item1.Dispose(); try { From 12df3814954a4251c30fd27ccdac89647da1a0a3 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 07:55:29 +0100 Subject: [PATCH 087/166] Grab content root from config and fix kestrel port bindings --- Emby.Server.Implementations/ApplicationHost.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 00a5f864df..09c40ea735 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -619,19 +619,23 @@ namespace Emby.Server.Implementations FindParts(); + string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(contentRoot)) + { + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + } + Host = new WebHostBuilder() .UseKestrel(options => { - options.Listen(IPAddress.Any, HttpPort); - options.Listen(IPAddress.Loopback, HttpPort); + options.ListenAnyIP(HttpPort); if (EnableHttps) { - options.Listen(IPAddress.Any, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); - options.Listen(IPAddress.Loopback, HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); + options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); } }) - .UseContentRoot(Path.Combine(Directory.GetCurrentDirectory(), "jellyfin-web", "src")) + .UseContentRoot(contentRoot) .ConfigureServices(services => { services.AddResponseCompression(); @@ -655,7 +659,7 @@ namespace Emby.Server.Implementations { if (!context.WebSockets.IsWebSocketRequest) { - await next(); + await next().ConfigureAwait(false); return; } @@ -665,7 +669,7 @@ namespace Emby.Server.Implementations { if (context.WebSockets.IsWebSocketRequest) { - await next(); + await next().ConfigureAwait(false); return; } From 51648a2a21e4829a66b0bff0ef1e0e23be1c6245 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 08:05:42 +0100 Subject: [PATCH 088/166] Remove unused _listener --- .../HttpServer/HttpListenerHost.cs | 40 +++++++------------ 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 19b1b26e89..293b28e74b 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -33,12 +33,8 @@ 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> WebSocketConnected; private readonly IServerConfigurationManager _config; @@ -68,7 +64,7 @@ namespace Emby.Server.Implementations.HttpServer IHttpListener socketListener) { _appHost = applicationHost; - _logger = loggerFactory.CreateLogger("HttpServer"); + Logger = loggerFactory.CreateLogger("HttpServer"); _config = config; DefaultRedirectPath = configuration["HttpListenerHost:DefaultRedirectPath"]; _networkManager = networkManager; @@ -85,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) { @@ -151,7 +147,7 @@ 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, @@ -220,11 +216,11 @@ 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; @@ -242,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)"); } } @@ -285,14 +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"); - } } public static string RemoveQueryStringByKey(string url, string key) @@ -613,11 +601,11 @@ namespace Emby.Server.Implementations.HttpServer var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) { - _logger.LogWarning("HTTP Response {StatusCode} to {RemoteIp}. Time (slow): {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); + 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); + Logger.LogDebug("HTTP Response {StatusCode} to {RemoteIp}. Time: {Elapsed:g}. {Url}", httpRes.StatusCode, remoteIp, elapsed, urlToLog); } } } @@ -630,7 +618,7 @@ 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; } @@ -644,7 +632,7 @@ 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; } @@ -696,7 +684,7 @@ namespace Emby.Server.Implementations.HttpServer ServiceController = new ServiceController(); - _logger.LogInformation("Calling ServiceStack AppHost.Init"); + Logger.LogInformation("Calling ServiceStack AppHost.Init"); var types = services.Select(r => r.GetType()).ToArray(); @@ -704,7 +692,7 @@ namespace Emby.Server.Implementations.HttpServer ResponseFilters = new Action[] { - new ResponseFilter(_logger).FilterResponse + new ResponseFilter(Logger).FilterResponse }; } @@ -834,7 +822,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 () => { @@ -844,7 +832,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); } })); From 2c26517172ca2c2f1df1c83d9300ad7c66667866 Mon Sep 17 00:00:00 2001 From: Phallacy Date: Mon, 4 Mar 2019 23:58:25 -0800 Subject: [PATCH 089/166] minor style fixes --- .../Cryptography/CryptographyProvider.cs | 42 +++++---- .../Library/DefaultAuthenticationProvider.cs | 39 ++++---- .../Library/UserManager.cs | 2 +- .../Cryptography/ICryptoProvider.cs | 42 ++++----- .../Cryptography/PasswordHash.cs | 92 +++++++++++-------- 5 files changed, 115 insertions(+), 102 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index 3c9403ba8e..cf1ea6efa3 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -11,17 +11,21 @@ namespace Emby.Server.Implementations.Cryptography { public class CryptographyProvider : ICryptoProvider { - private HashSet SupportedHashMethods; + private HashSet _supportedHashMethods; + public string DefaultHashMethod => "PBKDF2"; - private RandomNumberGenerator rng; - private int defaultiterations = 1000; + + private RandomNumberGenerator _randomNumberGenerator; + + private 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 - SupportedHashMethods = new HashSet() + _supportedHashMethods = new HashSet() { "MD5" ,"System.Security.Cryptography.MD5" @@ -38,7 +42,7 @@ namespace Emby.Server.Implementations.Cryptography ,"SHA-512" ,"System.Security.Cryptography.SHA512" }; - rng = RandomNumberGenerator.Create(); + _randomNumberGenerator = RandomNumberGenerator.Create(); } public Guid GetMD5(string str) @@ -72,7 +76,7 @@ namespace Emby.Server.Implementations.Cryptography public IEnumerable GetSupportedHashMethods() { - return SupportedHashMethods; + return _supportedHashMethods; } private byte[] PBKDF2(string method, byte[] bytes, byte[] salt, int iterations) @@ -86,12 +90,13 @@ namespace Emby.Server.Implementations.Cryptography return r.GetBytes(32); } } + throw new CryptographicException($"Cannot currently use PBKDF2 with requested hash method: {method}"); } - public byte[] ComputeHash(string HashMethod, byte[] bytes) + public byte[] ComputeHash(string hashMethod, byte[] bytes) { - return ComputeHash(HashMethod, bytes, new byte[0]); + return ComputeHash(hashMethod, bytes, new byte[0]); } public byte[] ComputeHashWithDefaultMethod(byte[] bytes) @@ -99,15 +104,15 @@ namespace Emby.Server.Implementations.Cryptography return ComputeHash(DefaultHashMethod, bytes); } - public byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt) + public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { - if(HashMethod == DefaultHashMethod) + if(hashMethod == DefaultHashMethod) { - return PBKDF2(HashMethod, bytes, salt, defaultiterations); + return PBKDF2(hashMethod, bytes, salt, _defaultIterations); } - else if (SupportedHashMethods.Contains(HashMethod)) + else if (_supportedHashMethods.Contains(hashMethod)) { - using (var h = HashAlgorithm.Create(HashMethod)) + using (var h = HashAlgorithm.Create(hashMethod)) { if (salt.Length == 0) { @@ -121,21 +126,21 @@ namespace Emby.Server.Implementations.Cryptography } else { - throw new CryptographicException($"Requested hash method is not supported: {HashMethod}"); + throw new CryptographicException($"Requested hash method is not supported: {hashMethod}"); } } public byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt) { - return PBKDF2(DefaultHashMethod, bytes, salt, defaultiterations); + return PBKDF2(DefaultHashMethod, bytes, salt, _defaultIterations); } public byte[] ComputeHash(PasswordHash hash) { - int iterations = defaultiterations; + int iterations = _defaultIterations; if (!hash.Parameters.ContainsKey("iterations")) { - hash.Parameters.Add("iterations", defaultiterations.ToString(CultureInfo.InvariantCulture)); + hash.Parameters.Add("iterations", _defaultIterations.ToString(CultureInfo.InvariantCulture)); } else { @@ -148,13 +153,14 @@ namespace Emby.Server.Implementations.Cryptography 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]; - rng.GetBytes(salt); + _randomNumberGenerator.GetBytes(salt); return salt; } } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 7ccdccc0ad..8f10b5a84e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -19,18 +19,16 @@ 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 + + // 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 Authenticate(string username, string password) { throw new NotImplementedException(); } - - - //This is the verson that we need to use for local users. Because reasons. + + // This is the verson that we need to use for local users. Because reasons. public Task Authenticate(string username, string password, User resolvedUser) { bool success = false; @@ -39,7 +37,7 @@ namespace Emby.Server.Implementations.Library throw new Exception("Invalid username or password"); } - //As long as jellyfin supports passwordless users, we need this little block here to accomodate + // As long as jellyfin supports passwordless users, we need this little block here to accomodate if (IsPasswordEmpty(resolvedUser, password)) { return Task.FromResult(new ProviderAuthenticationResult @@ -70,7 +68,7 @@ namespace Emby.Server.Implementations.Library if (CalculatedHashString == readyHash.Hash) { success = true; - //throw new Exception("Invalid username or password"); + // throw new Exception("Invalid username or password"); } } else @@ -78,7 +76,7 @@ namespace Emby.Server.Implementations.Library 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); + // var success = string.Equals(GetPasswordHash(resolvedUser), GetHashedString(resolvedUser, password), StringComparison.OrdinalIgnoreCase); if (!success) { @@ -91,8 +89,8 @@ 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. + // 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)) @@ -121,18 +119,13 @@ namespace Emby.Server.Implementations.Library private bool IsPasswordEmpty(User user, string password) { - if (string.IsNullOrEmpty(user.Password)) - { - return string.IsNullOrEmpty(password); - } - - return false; + return (string.IsNullOrEmpty(user.Password) && string.IsNullOrEmpty(password)); } public Task ChangePassword(User user, string newPassword) { ConvertPasswordFormat(user); - //This is needed to support changing a no password user to a password 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); @@ -184,7 +177,7 @@ namespace Emby.Server.Implementations.Library public string GetHashedString(User user, string str) { PasswordHash passwordHash; - if (String.IsNullOrEmpty(user.Password)) + if (string.IsNullOrEmpty(user.Password)) { passwordHash = new PasswordHash(_cryptographyProvider); } @@ -196,13 +189,13 @@ namespace Emby.Server.Implementations.Library if (passwordHash.SaltBytes != null) { - //the password is modern format with PBKDF and we should take advantage of that + // 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 + // 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/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 0f188ca757..57bf16364d 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library private readonly Func _dtoServiceFactory; private readonly IServerApplicationHost _appHost; private readonly IFileSystem _fileSystem; - + private IAuthenticationProvider[] _authenticationProviders; private DefaultAuthenticationProvider _defaultAuthenticationProvider; diff --git a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs index 8accc696e3..5988112c2e 100644 --- a/MediaBrowser.Model/Cryptography/ICryptoProvider.cs +++ b/MediaBrowser.Model/Cryptography/ICryptoProvider.cs @@ -1,22 +1,22 @@ -using System; -using System.IO; -using System.Collections.Generic; - -namespace MediaBrowser.Model.Cryptography -{ - public interface ICryptoProvider - { - Guid GetMD5(string str); - byte[] ComputeMD5(Stream str); - byte[] ComputeMD5(byte[] bytes); - byte[] ComputeSHA1(byte[] bytes); - IEnumerable GetSupportedHashMethods(); - byte[] ComputeHash(string HashMethod, byte[] bytes); - byte[] ComputeHashWithDefaultMethod(byte[] bytes); - byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); - byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); - byte[] ComputeHash(PasswordHash hash); +using System; +using System.IO; +using System.Collections.Generic; + +namespace MediaBrowser.Model.Cryptography +{ + public interface ICryptoProvider + { + Guid GetMD5(string str); + byte[] ComputeMD5(Stream str); + byte[] ComputeMD5(byte[] bytes); + byte[] ComputeSHA1(byte[] bytes); + IEnumerable GetSupportedHashMethods(); + byte[] ComputeHash(string HashMethod, byte[] bytes); + byte[] ComputeHashWithDefaultMethod(byte[] bytes); + byte[] ComputeHash(string HashMethod, byte[] bytes, byte[] salt); + byte[] ComputeHashWithDefaultMethod(byte[] bytes, byte[] salt); + byte[] ComputeHash(PasswordHash hash); byte[] GenerateSalt(); - string DefaultHashMethod { get; } - } -} + string DefaultHashMethod { get; } + } +} diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 49bd510e96..a528404045 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -6,27 +6,40 @@ namespace MediaBrowser.Model.Cryptography { public class PasswordHash { - //Defined from this hash storage spec - //https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - //$[$=(,=)*][$[$]] + // Defined from this hash storage spec + // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex + // rather than making them a BASE64 string with stripped padding - private string id; - private Dictionary parameters = new Dictionary(); - private string salt; - private byte[] saltBytes; - private string hash; - private byte[] hashBytes; - public string Id { get => id; set => id = value; } - public Dictionary Parameters { get => parameters; set => parameters = value; } - public string Salt { get => salt; set => salt = value; } - public byte[] SaltBytes { get => saltBytes; set => saltBytes = value; } - public string Hash { get => hash; set => hash = value; } - public byte[] HashBytes { get => hashBytes; set => hashBytes = value; } + private string _id; + + private Dictionary _parameters = new Dictionary(); + + private string _salt; + + private byte[] _saltBytes; + + private string _hash; + + private byte[] _hashBytes; + + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public string Salt { get => _salt; set => _salt = value; } + + public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } + + public string Hash { get => _hash; set => _hash = value; } + + public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); - id = splitted[1]; + _id = splitted[1]; if (splitted[2].Contains("=")) { foreach (string paramset in (splitted[2].Split(','))) @@ -36,7 +49,7 @@ namespace MediaBrowser.Model.Cryptography string[] fields = paramset.Split('='); if (fields.Length == 2) { - parameters.Add(fields[0], fields[1]); + _parameters.Add(fields[0], fields[1]); } else { @@ -46,32 +59,32 @@ namespace MediaBrowser.Model.Cryptography } if (splitted.Length == 5) { - salt = splitted[3]; - saltBytes = ConvertFromByteString(salt); - hash = splitted[4]; - hashBytes = ConvertFromByteString(hash); + _salt = splitted[3]; + _saltBytes = ConvertFromByteString(_salt); + _hash = splitted[4]; + _hashBytes = ConvertFromByteString(_hash); } else { - salt = string.Empty; - hash = splitted[3]; - hashBytes = ConvertFromByteString(hash); + _salt = string.Empty; + _hash = splitted[3]; + _hashBytes = ConvertFromByteString(_hash); } } else { if (splitted.Length == 4) { - salt = splitted[2]; - saltBytes = ConvertFromByteString(salt); - hash = splitted[3]; - hashBytes = ConvertFromByteString(hash); + _salt = splitted[2]; + _saltBytes = ConvertFromByteString(_salt); + _hash = splitted[3]; + _hashBytes = ConvertFromByteString(_hash); } else { - salt = string.Empty; - hash = splitted[2]; - hashBytes = ConvertFromByteString(hash); + _salt = string.Empty; + _hash = splitted[2]; + _hashBytes = ConvertFromByteString(_hash); } } @@ -80,9 +93,9 @@ namespace MediaBrowser.Model.Cryptography public PasswordHash(ICryptoProvider cryptoProvider) { - id = cryptoProvider.DefaultHashMethod; - saltBytes = cryptoProvider.GenerateSalt(); - salt = ConvertToByteString(SaltBytes); + _id = cryptoProvider.DefaultHashMethod; + _saltBytes = cryptoProvider.GenerateSalt(); + _salt = ConvertToByteString(SaltBytes); } public static byte[] ConvertFromByteString(string byteString) @@ -92,6 +105,7 @@ namespace MediaBrowser.Model.Cryptography { Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } + return Bytes.ToArray(); } @@ -103,7 +117,7 @@ namespace MediaBrowser.Model.Cryptography private string SerializeParameters() { string ReturnString = string.Empty; - foreach (var KVP in parameters) + foreach (var KVP in _parameters) { ReturnString += $",{KVP.Key}={KVP.Value}"; } @@ -118,19 +132,19 @@ namespace MediaBrowser.Model.Cryptography public override string ToString() { - string outString = "$" +id; + string outString = "$" +_id; string paramstring = SerializeParameters(); if (!string.IsNullOrEmpty(paramstring)) { outString += $"${paramstring}"; } - if (!string.IsNullOrEmpty(salt)) + if (!string.IsNullOrEmpty(_salt)) { - outString += $"${salt}"; + outString += $"${_salt}"; } - outString += $"${hash}"; + outString += $"${_hash}"; return outString; } } From bc00617df7eee51a80302816bef9a797455e7780 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 10:26:43 +0100 Subject: [PATCH 090/166] Remove unused Brotli compressor --- Emby.Server.Implementations/ApplicationHost.cs | 7 +------ .../HttpServer/HttpResultFactory.cs | 15 +-------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 09c40ea735..deb1d3cee0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -741,7 +741,7 @@ namespace Emby.Server.Implementations ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); - HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, CreateBrotliCompressor()); + HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer); serviceCollection.AddSingleton(HttpResultFactory); serviceCollection.AddSingleton(this); @@ -896,11 +896,6 @@ namespace Emby.Server.Implementations _serviceProvider = serviceCollection.BuildServiceProvider(); } - protected virtual IBrotliCompressor CreateBrotliCompressor() - { - return null; - } - public virtual string PackageRuntime => "netcore"; public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index 52c8221f68..be50719d85 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -35,16 +35,13 @@ namespace Emby.Server.Implementations.HttpServer private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; - private IBrotliCompressor _brotliCompressor; - /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IBrotliCompressor brotliCompressor) + public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; - _brotliCompressor = brotliCompressor; _logger = loggerfactory.CreateLogger("HttpResultFactory"); } @@ -350,11 +347,6 @@ 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); @@ -368,11 +360,6 @@ namespace Emby.Server.Implementations.HttpServer 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 From 318e0d4a2449baff806afb4ee22dc0f4021ca180 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 10:27:25 +0100 Subject: [PATCH 091/166] Add GetValueOrDefault dictionary extension --- Emby.Dlna/PlayTo/PlayToController.cs | 22 +++++++++---------- .../Extensions/CollectionExtensions.cs | 14 ++++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 MediaBrowser.Common/Extensions/CollectionExtensions.cs diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 2da8bb8609..8d7d59249a 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Dlna.Didl; using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; @@ -17,7 +18,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Events; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Services; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; @@ -849,13 +849,13 @@ namespace Emby.Dlna.PlayTo if (index == -1) return request; var query = url.Substring(index + 1); - Dictionary values = QueryHelpers.ParseQuery(query); + Dictionary values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString()); - request.DeviceProfileId = values["DeviceProfileId"].ToString(); - request.DeviceId = values["DeviceId"].ToString(); - request.MediaSourceId = values["MediaSourceId"].ToString(); - request.LiveStreamId = values["LiveStreamId"].ToString(); - request.IsDirectStream = string.Equals("true", values["Static"].ToString(), StringComparison.OrdinalIgnoreCase); + request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId"); + request.DeviceId = values.GetValueOrDefault("DeviceId"); + request.MediaSourceId = values.GetValueOrDefault("MediaSourceId"); + request.LiveStreamId = values.GetValueOrDefault("LiveStreamId"); + request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase); request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex"); request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex"); @@ -869,9 +869,9 @@ namespace Emby.Dlna.PlayTo } } - private static int? GetIntValue(IReadOnlyDictionary values, string name) + private static int? GetIntValue(IReadOnlyDictionary values, string name) { - var value = values[name].ToString(); + var value = values.GetValueOrDefault(name); if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { @@ -881,9 +881,9 @@ namespace Emby.Dlna.PlayTo return null; } - private static long GetLongValue(IReadOnlyDictionary values, string name) + private static long GetLongValue(IReadOnlyDictionary values, string name) { - var value = values[name].ToString(); + var value = values.GetValueOrDefault(name); if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { diff --git a/MediaBrowser.Common/Extensions/CollectionExtensions.cs b/MediaBrowser.Common/Extensions/CollectionExtensions.cs new file mode 100644 index 0000000000..f7c0e3cf04 --- /dev/null +++ b/MediaBrowser.Common/Extensions/CollectionExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Common.Extensions +{ + // The MS CollectionExtensions are only available in netcoreapp + public static class CollectionExtensions + { + public static TValue GetValueOrDefault (this IReadOnlyDictionary dictionary, TKey key) + { + dictionary.TryGetValue(key, out var ret); + return ret; + } + } +} From 942c400c19157ca437c7c885677a0a92fdd120f5 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Tue, 5 Mar 2019 17:12:56 +0100 Subject: [PATCH 092/166] Update README with Azure Pipelines status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d869c89789..1f635bdd25 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ GPL 2.0 License Current Release Translations -Build Status +Azure DevOps builds Docker Pull Count
Donate From 78742b8e4c658b1f02c9c040b8abb8ee5742bed4 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:20:28 +0100 Subject: [PATCH 093/166] Switch to HeaderNames instead of hardcoded strings (and other header related fixes) --- .../HttpClientManager/HttpClientManager.cs | 5 +- .../HttpServer/FileWriter.cs | 9 ++- .../HttpServer/HttpResultFactory.cs | 41 +++++----- .../HttpServer/RangeRequestWriter.cs | 7 +- .../HttpServer/ResponseFilter.cs | 7 +- .../Security/AuthorizationContext.cs | 3 +- .../HttpServer/StreamWriter.cs | 5 +- .../LiveTv/Listings/SchedulesDirect.cs | 5 +- .../LiveTv/TunerHosts/M3UTunerHost.cs | 3 +- .../SocketSharp/RequestMono.cs | 5 +- MediaBrowser.Api/Images/ImageService.cs | 3 +- MediaBrowser.Api/Library/LibraryService.cs | 3 +- MediaBrowser.Api/LiveTv/LiveTvService.cs | 15 ++-- .../BaseProgressiveStreamingService.cs | 81 ++++--------------- MediaBrowser.Common/Net/HttpRequestOptions.cs | 9 ++- 15 files changed, 82 insertions(+), 119 deletions(-) diff --git a/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs b/Emby.Server.Implementations/HttpClientManager/HttpClientManager.cs index ef82d1d9ee..1bebdd1637 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; diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 1375089e39..303db99801 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -9,6 +9,7 @@ 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 { @@ -56,10 +57,10 @@ namespace Emby.Server.Implementations.HttpServer FileSystem = fileSystem; 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)) { @@ -97,8 +98,8 @@ namespace Emby.Server.Implementations.HttpServer // Content-Length is the length of what we're serving, not the original content var lengthString = RangeLength.ToString(UsCulture); - 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); } diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index be50719d85..f4fe6b611a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.HttpServer public object GetRedirectResult(string url) { var responseHeaders = new Dictionary(); - responseHeaders["Location"] = url; + responseHeaders[HeaderNames.Location] = url; var result = new HttpResult(Array.Empty(), "text/plain", HttpStatusCode.Redirect); @@ -96,9 +96,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - 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); @@ -142,9 +142,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -186,9 +186,9 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(); } - if (addCachePrevention && !responseHeaders.TryGetValue("Expires", out string _)) + if (addCachePrevention && !responseHeaders.TryGetValue(HeaderNames.Expires, out string _)) { - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; } AddResponseHeaders(result, responseHeaders); @@ -213,7 +213,7 @@ namespace Emby.Server.Implementations.HttpServer responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); } - responseHeaders["Expires"] = "-1"; + responseHeaders[HeaderNames.Expires] = "-1"; return ToOptimizedResultInternal(requestContext, result, responseHeaders); } @@ -245,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer private static string GetCompressionType(IRequest request) { - var acceptEncoding = request.Headers["Accept-Encoding"].ToString(); + var acceptEncoding = request.Headers[HeaderNames.AcceptEncoding].ToString(); if (string.IsNullOrEmpty(acceptEncoding)) { @@ -325,9 +325,9 @@ 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; @@ -537,7 +537,7 @@ namespace Emby.Server.Implementations.HttpServer AddCachingHeaders(responseHeaders, options.CacheDuration, false, options.DateLastModified); AddAgeHeader(responseHeaders, options.DateLastModified); - var rangeHeader = requestContext.Headers["Range"]; + var rangeHeader = requestContext.Headers[HeaderNames.Range]; if (!isHeadRequest && !string.IsNullOrEmpty(options.Path)) { @@ -606,23 +606,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(); } } @@ -635,7 +635,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); } } @@ -693,9 +693,4 @@ namespace Emby.Server.Implementations.HttpServer } } } - - public interface IBrotliCompressor - { - byte[] Compress(byte[] content); - } } diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index 8904e11d30..a0bfe5d8f9 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); @@ -96,7 +97,7 @@ namespace Emby.Server.Implementations.HttpServer RangeLength = 1 + RangeEnd - RangeStart; // Content-Length is the length of what we're serving, not the original content - Headers["Content-Range"] = $"bytes {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 ae6a6576e0..a53d9bf0b9 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 { @@ -44,13 +45,13 @@ 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); diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index cab41e65b9..276312a300 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 90354385dc..66a13e20df 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 { @@ -52,7 +53,7 @@ namespace Emby.Server.Implementations.HttpServer SourceStream = source; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; } /// @@ -69,7 +70,7 @@ namespace Emby.Server.Implementations.HttpServer SourceBytes = source; - Headers["Content-Type"] = contentType; + Headers[HeaderNames.ContentType] = contentType; } public async Task WriteToAsync(Stream responseStream, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index 0bbffb824b..4137760d07 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/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs index fdaaf0bae7..588dcb843b 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 { @@ -145,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/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index f73adc5ff8..82bcf2f043 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -7,6 +7,7 @@ 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 { @@ -114,9 +115,9 @@ namespace Emby.Server.Implementations.SocketSharp return form; } - public string Accept => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString(); + public string Accept => StringValues.IsNullOrEmpty(request.Headers[HeaderNames.Accept]) ? null : request.Headers[HeaderNames.Accept].ToString(); - public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].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; } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 61db7b8d47..c25204bf2d 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -18,6 +18,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Images { @@ -634,7 +635,7 @@ namespace MediaBrowser.Api.Images var imageResult = await _imageProcessor.ProcessImage(options).ConfigureAwait(false); - headers["Vary"] = "Accept"; + headers[HeaderNames.Vary] = HeaderNames.Accept; return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index d44b07256d..8eefbdf2ca 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -29,6 +29,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Library { @@ -827,7 +828,7 @@ namespace MediaBrowser.Api.Library var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty); if (!string.IsNullOrWhiteSpace(filename)) { - headers["Content-Disposition"] = "attachment; filename=\"" + filename + "\""; + headers[HeaderNames.ContentDisposition] = "attachment; filename=\"" + filename + "\""; } return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 8fdd726b7b..88ed734569 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -24,6 +24,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.LiveTv { @@ -750,9 +751,10 @@ namespace MediaBrowser.Api.LiveTv throw new FileNotFoundException(); } - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType(path); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) + }; return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment) { @@ -772,9 +774,10 @@ namespace MediaBrowser.Api.LiveTv var directStreamProvider = liveStreamInfo; - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - - outputHeaders["Content-Type"] = Model.Net.MimeTypes.GetMimeType("file." + request.Container); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container) + }; return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment) { diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 3e74d59db7..fc81e532d8 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -17,6 +17,7 @@ using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive { @@ -154,7 +155,7 @@ namespace MediaBrowser.Api.Playback.Progressive var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); // TODO: Don't hardcode this - outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); + outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts"); return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) { @@ -196,9 +197,11 @@ namespace MediaBrowser.Api.Playback.Progressive { if (state.MediaSource.IsInfiniteStream) { - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = contentType + }; - outputHeaders["Content-Type"] = contentType; return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) { @@ -298,16 +301,16 @@ namespace MediaBrowser.Api.Playback.Progressive if (trySupportSeek) { - if (!string.IsNullOrWhiteSpace(Request.QueryString["Range"])) + if (!string.IsNullOrWhiteSpace(Request.QueryString[HeaderNames.Range])) { - options.RequestHeaders["Range"] = Request.QueryString["Range"]; + options.RequestHeaders[HeaderNames.Range] = Request.QueryString[HeaderNames.Range]; } } var response = await HttpClient.GetResponse(options).ConfigureAwait(false); if (trySupportSeek) { - foreach (var name in new[] { "Content-Range", "Accept-Ranges" }) + foreach (var name in new[] { HeaderNames.ContentRange, HeaderNames.AcceptRanges }) { var val = response.Headers[name]; if (!string.IsNullOrWhiteSpace(val)) @@ -318,14 +321,7 @@ namespace MediaBrowser.Api.Playback.Progressive } else { - responseHeaders["Accept-Ranges"] = "none"; - } - - // Seeing cases of -1 here - if (response.ContentLength.HasValue && response.ContentLength.Value >= 0) - { - // TODO - //responseHeaders["Content-Length"] = response.ContentLength.Value.ToString(UsCulture); + responseHeaders[HeaderNames.AcceptRanges] = "none"; } if (isHeadRequest) @@ -338,7 +334,7 @@ namespace MediaBrowser.Api.Playback.Progressive var result = new StaticRemoteStreamWriter(response); - result.Headers["Content-Type"] = response.ContentType; + result.Headers[HeaderNames.ContentType] = response.ContentType; // Add the response headers to the result object foreach (var header in responseHeaders) @@ -362,42 +358,14 @@ namespace MediaBrowser.Api.Playback.Progressive // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; - responseHeaders["Accept-Ranges"] = "none"; + responseHeaders[HeaderNames.AcceptRanges] = "none"; var contentType = state.GetMimeType(outputPath); - // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response - // What we really want to do is hunt that down and remove that - var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; - - if (contentLength.HasValue) - { - responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); - } - // Headers only if (isHeadRequest) { - var streamResult = ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); - - var hasHeaders = streamResult as IHasHeaders; - if (hasHeaders != null) - { - // TODO - //if (contentLength.HasValue) - //{ - // hasHeaders.Headers["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); - //} - //else - //{ - if (hasHeaders.Headers.ContainsKey("Content-Length")) - { - hasHeaders.Headers.Remove("Content-Length"); - } - //} - } - - return streamResult; + return ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); @@ -416,9 +384,11 @@ namespace MediaBrowser.Api.Playback.Progressive state.Dispose(); } - var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + var outputHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [HeaderNames.ContentType] = contentType + }; - outputHeaders["Content-Type"] = contentType; // Add the response headers to the result object foreach (var item in responseHeaders) @@ -433,22 +403,5 @@ namespace MediaBrowser.Api.Playback.Progressive transcodingLock.Release(); } } - - /// - /// Gets the length of the estimated content. - /// - /// The state. - /// System.Nullable{System.Int64}. - private long? GetEstimatedContentLength(StreamState state) - { - var totalBitrate = state.TotalOutputBitrate ?? 0; - - if (totalBitrate > 0 && state.RunTimeTicks.HasValue) - { - return Convert.ToInt64(totalBitrate * TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds / 8); - } - - return null; - } } } diff --git a/MediaBrowser.Common/Net/HttpRequestOptions.cs b/MediaBrowser.Common/Net/HttpRequestOptions.cs index dadac5e03d..bea178517b 100644 --- a/MediaBrowser.Common/Net/HttpRequestOptions.cs +++ b/MediaBrowser.Common/Net/HttpRequestOptions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; +using Microsoft.Net.Http.Headers; namespace MediaBrowser.Common.Net { @@ -24,8 +25,8 @@ namespace MediaBrowser.Common.Net /// The accept header. public string AcceptHeader { - get => GetHeaderValue("Accept"); - set => RequestHeaders["Accept"] = value; + get => GetHeaderValue(HeaderNames.Accept); + set => RequestHeaders[HeaderNames.Accept] = value; } /// /// Gets or sets the cancellation token. @@ -45,8 +46,8 @@ namespace MediaBrowser.Common.Net /// The user agent. public string UserAgent { - get => GetHeaderValue("User-Agent"); - set => RequestHeaders["User-Agent"] = value; + get => GetHeaderValue(HeaderNames.UserAgent); + set => RequestHeaders[HeaderNames.UserAgent] = value; } /// From df92df7bd63f9fd19ed195f668b45d52d6bd3abc Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:22:41 +0100 Subject: [PATCH 094/166] Remove BOM --- Jellyfin.Server/Jellyfin.Server.csproj | 2 +- MediaBrowser.Controller/MediaBrowser.Controller.csproj | 2 +- MediaBrowser.sln | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 94a7e58cb5..bd670df527 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -1,4 +1,4 @@ - + jellyfin diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 5898d8f984..01893f1b55 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors diff --git a/MediaBrowser.sln b/MediaBrowser.sln index b1d3fcdda8..e43a582242 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26730.3 From 9a4a01fb0e94d8ffbc00f5932f284f7faeb0ee69 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 19:32:22 +0100 Subject: [PATCH 095/166] Fix DI in FileWriter.TransmitFile --- .../ApplicationHost.cs | 2 +- .../HttpServer/FileWriter.cs | 30 ++++++++----------- .../HttpServer/HttpResultFactory.cs | 6 ++-- .../SocketSharp/WebSocketSharpResponse.cs | 2 -- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index deb1d3cee0..93147539c6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -741,7 +741,7 @@ namespace Emby.Server.Implementations ZipClient = new ZipClient(); serviceCollection.AddSingleton(ZipClient); - HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer); + HttpResultFactory = new HttpResultFactory(LoggerFactory, FileSystemManager, JsonSerializer, StreamHelper); serviceCollection.AddSingleton(HttpResultFactory); serviceCollection.AddSingleton(this); diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 303db99801..c4a170eaae 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -15,6 +15,7 @@ namespace Emby.Server.Implementations.HttpServer { public class FileWriter : IHttpResult { + private readonly IStreamHelper _streamHelper; private ILogger Logger { get; set; } public IFileSystem FileSystem { get; } @@ -45,13 +46,15 @@ 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; + Path = path; Logger = logger; FileSystem = fileSystem; @@ -147,8 +150,7 @@ namespace Emby.Server.Implementations.HttpServer } } - private string[] SkipLogExtensions = new string[] - { + private readonly string[] SkipLogExtensions = { ".js", ".html", ".css" @@ -165,8 +167,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); @@ -175,20 +179,15 @@ namespace Emby.Server.Implementations.HttpServer Logger.LogDebug("Transmit file {0}", path); } - //var count = FileShare == FileShareMode.ReadWrite ? TotalContentLength : 0; - // TODO not DI friendly lol - await response.TransmitFile(path, 0, 0, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); - return; + offset = 0; + count = 0; } - // TODO not DI friendly lol - await response.TransmitFile(path, RangeStart, RangeLength, FileShare, FileSystem, new StreamHelper(), cancellationToken).ConfigureAwait(false); + + await response.TransmitFile(path, offset, count, FileShare, FileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); } finally { - if (OnComplete != null) - { - OnComplete(); - } + OnComplete?.Invoke(); } } @@ -205,8 +204,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/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs index f4fe6b611a..4632658626 100644 --- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -34,14 +34,16 @@ namespace Emby.Server.Implementations.HttpServer private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; + private readonly IStreamHelper _streamHelper; /// /// Initializes a new instance of the class. /// - public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer) + public HttpResultFactory(ILoggerFactory loggerfactory, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IStreamHelper streamHelper) { _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; + _streamHelper = streamHelper; _logger = loggerfactory.CreateLogger("HttpResultFactory"); } @@ -541,7 +543,7 @@ namespace Emby.Server.Implementations.HttpServer 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, diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index 5964107651..a7e3e6c700 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -78,8 +78,6 @@ namespace Emby.Server.Implementations.SocketSharp const int StreamCopyToBufferSize = 81920; public async Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken) { - // TODO - // return _response.TransmitFile(path, offset, count, fileShareMode, cancellationToken); var allowAsync = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); //if (count <= 0) From 913e80fd5527775ea71d6f9606f49c5494cb7b63 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 20:35:07 +0100 Subject: [PATCH 096/166] Add ProcessWebSocketRequest to IHttpListener --- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 5 ++--- Emby.Server.Implementations/HttpServer/IHttpListener.cs | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index 293b28e74b..d1b1b5b242 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -756,11 +756,10 @@ namespace Emby.Server.Implementations.HttpServer public Task ProcessWebSocketRequest(HttpContext context) { - // TODO - return ((WebSocketSharpListener)_socketListener).ProcessWebSocketRequest(context); + return _socketListener.ProcessWebSocketRequest(context); } - //TODO Add Jellyfin Route Path Normalizer + //TODO Add Jellyfin Route Path Normalizer private static string NormalizeEmbyRoutePath(string path) { if (path.StartsWith("/", StringComparison.OrdinalIgnoreCase)) diff --git a/Emby.Server.Implementations/HttpServer/IHttpListener.cs b/Emby.Server.Implementations/HttpServer/IHttpListener.cs index 1ef65d9d73..005656d2c1 100644 --- a/Emby.Server.Implementations/HttpServer/IHttpListener.cs +++ b/Emby.Server.Implementations/HttpServer/IHttpListener.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Net; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace Emby.Server.Implementations.HttpServer { @@ -30,5 +31,7 @@ namespace Emby.Server.Implementations.HttpServer /// Stops this instance. /// Task Stop(); + + Task ProcessWebSocketRequest(HttpContext ctx); } } From 446f9bf81fa3ac737acda71d57fdc7289c227cef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 5 Mar 2019 20:48:04 +0100 Subject: [PATCH 097/166] Remove more Content-Length references --- .../HttpServer/FileWriter.cs | 4 +-- .../HttpServer/RangeRequestWriter.cs | 1 - .../Services/QueryParamCollection.cs | 26 +++---------------- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index c4a170eaae..78e2015b1e 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -99,12 +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); 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); } /// diff --git a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs index a0bfe5d8f9..449159834a 100644 --- a/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs +++ b/Emby.Server.Implementations/HttpServer/RangeRequestWriter.cs @@ -96,7 +96,6 @@ 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[HeaderNames.ContentRange] = $"bytes {RangeStart}-{RangeEnd}/{TotalContentLength}"; if (RangeStart > 0 && SourceStream.CanSeek) diff --git a/MediaBrowser.Model/Services/QueryParamCollection.cs b/MediaBrowser.Model/Services/QueryParamCollection.cs index 4631a3b633..7708db00a8 100644 --- a/MediaBrowser.Model/Services/QueryParamCollection.cs +++ b/MediaBrowser.Model/Services/QueryParamCollection.cs @@ -25,16 +25,12 @@ namespace MediaBrowser.Model.Services /// /// Adds a new query parameter. /// - public virtual void Add(string key, string value) + public void Add(string key, string value) { - if (string.Equals(key, "content-length", StringComparison.OrdinalIgnoreCase)) - { - return; - } Add(new NameValuePair(key, value)); } - public virtual void Set(string key, string value) + private void Set(string key, string value) { if (string.IsNullOrEmpty(value)) { @@ -62,7 +58,7 @@ namespace MediaBrowser.Model.Services Add(key, value); } - public string Get(string name) + private string Get(string name) { var stringComparison = GetStringComparison(); @@ -77,7 +73,7 @@ namespace MediaBrowser.Model.Services return null; } - public virtual List GetItems(string name) + private List GetItems(string name) { var stringComparison = GetStringComparison(); @@ -111,20 +107,6 @@ namespace MediaBrowser.Model.Services return list; } - public Dictionary ToDictionary() - { - var stringComparer = GetStringComparer(); - - var headers = new Dictionary(stringComparer); - - foreach (var pair in this) - { - headers[pair.Name] = pair.Value; - } - - return headers; - } - public IEnumerable Keys { get From 20775116f76b12bf77672fa37c4ea5f82b69f157 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Fri, 8 Feb 2019 13:35:26 +0000 Subject: [PATCH 098/166] Reworked FFmpeg path discovery and always display to user 1) Reworked FFmpeg and FFprobe path discovery (CLI switch, Custom xml, system $PATH, UI update trigger). Removed FFMpeg folder from Emby.Server.Implementations. All path discovery now in MediaEncoder. 2) Always display FFmpeg path to user in Transcode page. 3) Allow user to remove a Custome FFmpeg path and return to using system $PATH (or --ffmpeg if available). 4) Remove unused code associated with 'prebuilt' FFmpeg. 5) Much improved logging during path discovery. --- .../ApplicationHost.cs | 72 +-- .../FFMpeg/FFMpegInfo.cs | 24 - .../FFMpeg/FFMpegInstallInfo.cs | 17 - .../FFMpeg/FFMpegLoader.cs | 132 ----- .../Encoder/EncoderValidator.cs | 2 +- .../Encoder/MediaEncoder.cs | 509 +++++++++--------- .../Configuration/EncodingOptions.cs | 3 +- 7 files changed, 250 insertions(+), 509 deletions(-) delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegInstallInfo.cs delete mode 100644 Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 94d2cd5daf..2c0d0e7469 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -28,7 +28,6 @@ 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; @@ -792,7 +791,8 @@ 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); @@ -908,83 +908,25 @@ 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(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo()) - .GetFFMpegInfo(StartupOptions); - } - /// /// Registers the media encoder. /// /// Task. - private void RegisterMediaEncoder(IServiceCollection serviceCollection) + private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) { - 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( + MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( LoggerFactory, JsonSerializer, - encoderPath, - probePath, - hasExternalEncoder, + StartupOptions.FFmpegPath, + StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, - LiveTvManager, - IsoManager, - LibraryManager, - ChannelManager, - SessionManager, () => SubtitleEncoder, () => MediaSourceManager, - HttpClient, - ZipClient, ProcessFactory, 5000); - MediaEncoder = mediaEncoder; - serviceCollection.AddSingleton(MediaEncoder); + RegisterSingleInstance(MediaEncoder); } /// diff --git a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs b/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs deleted file mode 100644 index 60cd7b3d72..0000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Emby.Server.Implementations.FFMpeg -{ - /// - /// Class FFMpegInfo - /// - public class FFMpegInfo - { - /// - /// Gets or sets the path. - /// - /// The path. - public string EncoderPath { get; set; } - /// - /// Gets or sets the probe path. - /// - /// The probe path. - public string ProbePath { get; set; } - /// - /// Gets or sets the version. - /// - /// The version. - 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 fa9cb5e01b..0000000000 --- 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 bbf51dd246..0000000000 --- a/Emby.Server.Implementations/FFMpeg/FFMpegLoader.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.IO; - -namespace Emby.Server.Implementations.FFMpeg -{ - public class FFMpegLoader - { - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly FFMpegInstallInfo _ffmpegInstallInfo; - - public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo) - { - _appPaths = appPaths; - _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 { 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)) - { - 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/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f725d2c019..1eeea87a06 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -19,7 +19,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _processFactory = processFactory; } - public (IEnumerable decoders, IEnumerable encoders) Validate(string encoderPath) + public (IEnumerable decoders, IEnumerable encoders) GetAvailableCoders(string encoderPath) { _logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 7f29c06b4c..36d72cad90 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -7,13 +7,9 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Diagnostics; @@ -32,323 +28,288 @@ namespace MediaBrowser.MediaEncoding.Encoder public class MediaEncoder : IMediaEncoder, IDisposable { /// - /// The _logger + /// Gets the encoder path. /// + /// The encoder path. + public string EncoderPath => FFmpegPath; + + /// + /// External: path supplied via command line + /// Custom: coming from UI or config/encoding.xml file + /// System: FFmpeg found in system $PATH + /// null: No FFmpeg found + /// + public string EncoderLocationType { get; private set; } + private readonly ILogger _logger; - - /// - /// Gets the json serializer. - /// - /// The json serializer. private readonly IJsonSerializer _jsonSerializer; - - /// - /// The _thumbnail resource pool - /// - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); - - public string FFMpegPath { get; private set; } - - public string FFProbePath { get; private set; } - + private string FFmpegPath { get; set; } + private string FFprobePath { get; set; } protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; - protected readonly ILiveTvManager LiveTvManager; - protected readonly IIsoManager IsoManager; - protected readonly ILibraryManager LibraryManager; - protected readonly IChannelManager ChannelManager; - protected readonly ISessionManager SessionManager; protected readonly Func SubtitleEncoder; protected readonly Func MediaSourceManager; - private readonly IHttpClient _httpClient; - private readonly IZipClient _zipClient; private readonly IProcessFactory _processFactory; - - private readonly List _runningProcesses = new List(); - private readonly bool _hasExternalEncoder; - private readonly string _originalFFMpegPath; - private readonly string _originalFFProbePath; private readonly int DefaultImageExtractionTimeoutMs; + private readonly string StartupOptionFFmpegPath; + private readonly string StartupOptionFFprobePath; + + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); + private readonly List _runningProcesses = new List(); public MediaEncoder( ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, - string ffMpegPath, - string ffProbePath, - bool hasExternalEncoder, + string startupOptionsFFmpegPath, + string startupOptionsFFprobePath, IServerConfigurationManager configurationManager, IFileSystem fileSystem, - ILiveTvManager liveTvManager, - IIsoManager isoManager, - ILibraryManager libraryManager, - IChannelManager channelManager, - ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, - IHttpClient httpClient, - IZipClient zipClient, IProcessFactory processFactory, int defaultImageExtractionTimeoutMs) { _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); _jsonSerializer = jsonSerializer; + StartupOptionFFmpegPath = startupOptionsFFmpegPath; + StartupOptionFFprobePath = startupOptionsFFprobePath; ConfigurationManager = configurationManager; FileSystem = fileSystem; - LiveTvManager = liveTvManager; - IsoManager = isoManager; - LibraryManager = libraryManager; - ChannelManager = channelManager; - SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; - MediaSourceManager = mediaSourceManager; - _httpClient = httpClient; - _zipClient = zipClient; _processFactory = processFactory; DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs; - FFProbePath = ffProbePath; - FFMpegPath = ffMpegPath; - _originalFFProbePath = ffProbePath; - _originalFFMpegPath = ffMpegPath; - _hasExternalEncoder = hasExternalEncoder; } - public string EncoderLocationType + /// + /// Run at startup or if the user removes a Custom path from transcode page. + /// Sets global variables FFmpegPath and EncoderLocationType. + /// If startup options --ffprobe is given then FFprobePath is set too. + /// + public void Init() { - get + // 1) If given, use the --ffmpeg CLI switch + if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath)) { - if (_hasExternalEncoder) + _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg"); + EncoderLocationType = "External"; + } + + // 2) Try Custom path stroed in config/encoding xml file under tag + else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom)) + { + _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file"); + EncoderLocationType = "Custom"; + } + + // 3) Search system $PATH environment variable for valid FFmpeg + else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg"))) + { + _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg"); + EncoderLocationType = "System"; + } + else + { + _logger.LogError("FFmpeg: No suitable executable found"); + FFmpegPath = null; + EncoderLocationType = null; + } + + // If given, use the --ffprobe CLI switch + if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath)) + { + _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe"); + } + else + { + // FFprobe path from command line is no good, so set to null and let ReInit() try + // and set using the FFmpeg path. + FFprobePath = null; + } + + ReInit(); + } + + /// + /// Writes the currently used FFmpeg to config/encoding.xml file. + /// Sets the FFprobe path if not currently set. + /// Interrogates the FFmpeg tool to identify what encoders/decodres are available. + /// + private void ReInit() + { + // Write the FFmpeg path to the config/encoding.xml file so it appears in UI + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPath = FFmpegPath ?? string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); + + // Only if mpeg path is set, try and set path to probe + if (FFmpegPath != null) + { + // Probe would be null here if no valid --ffprobe path was given + // at startup, or we're performing ReInit following mpeg path update from UI + if (FFprobePath == null) { - return "External"; + // Use the mpeg path to create a probe path + if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath))) + { + _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg"); + } + else + { + _logger.LogError("FFprobe: No suitable executable found"); + } } - if (string.IsNullOrWhiteSpace(FFMpegPath)) - { - return null; - } + // Interrogate to understand what coders it supports + var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath); - if (IsSystemInstalledPath(FFMpegPath)) - { - return "System"; - } + SetAvailableDecoders(result.decoders); + SetAvailableEncoders(result.encoders); + } - return "Custom"; + // Stamp FFmpeg paths to the log file + LogPaths(); + } + + /// + /// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use. + /// + /// + /// + public void UpdateEncoderPath(string path, string pathType) + { + _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); + + if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Unexpected pathType value"); + } + else + { + if (string.IsNullOrWhiteSpace(path)) + { + // User had cleared the cutom path in UI. Clear the Custom config + // setting and peform full Init to relook any CLI switches and system $PATH + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); + + Init(); + } + else if (!File.Exists(path) && !Directory.Exists(path)) + { + // Given path is neither file or folder + throw new ResourceNotFoundException(); + } + else + { + // Supplied path could be either file path or folder path. + // Resolve down to file path and validate + path = GetEncoderPath(path); + + if (path == null) + { + throw new ResourceNotFoundException("FFmpeg not found"); + } + else if (!ValidatePathFFmpeg("New From UI", path)) + { + throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required"); + } + else + { + EncoderLocationType = "Custom"; + + // Write the validated mpeg path to the xml as + // This ensures its not lost on new startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = FFmpegPath; + ConfigurationManager.SaveConfiguration("encoding", config); + + FFprobePath = null; // Clear probe path so it gets relooked in ReInit() + + ReInit(); + } + } } } - private bool IsSystemInstalledPath(string path) + private bool ValidatePath(string type, string path) { - if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1) + if (!string.IsNullOrEmpty(path)) { + if (File.Exists(path)) + { + var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); + + if (valid == true) + { + return true; + } + else + { + _logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path); + } + } + else + { + _logger.LogError("{0}: File not found: {1}", type, path); + } + } + + return false; + } + + private bool ValidatePathFFmpeg(string comment, string path) + { + if (ValidatePath("FFmpeg: " + comment, path) == true) + { + FFmpegPath = path; return true; } return false; } - public void Init() + private bool ValidatePathFFprobe(string comment, string path) { - InitPaths(); - - if (!string.IsNullOrWhiteSpace(FFMpegPath)) + if (ValidatePath("FFprobe: " + comment, path) == true) { - var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath); - - SetAvailableDecoders(result.decoders); - SetAvailableEncoders(result.encoders); + FFprobePath = path; + return true; } + + return false; } - private void InitPaths() + private string GetEncoderPath(string path) { - ConfigureEncoderPaths(); - - if (_hasExternalEncoder) + if (Directory.Exists(path)) { - LogPaths(); - return; + return GetEncoderPathFromDirectory(path); } - // If the path was passed in, save it into config now. - var encodingOptions = GetEncodingOptions(); - var appPath = encodingOptions.EncoderAppPath; - - var valueToSave = FFMpegPath; - - if (!string.IsNullOrWhiteSpace(valueToSave)) + if (File.Exists(path)) { - // if using system variable, don't save this. - if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder) - { - valueToSave = null; - } + return path; } - if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal)) - { - encodingOptions.EncoderAppPath = valueToSave; - ConfigurationManager.SaveConfiguration("encoding", encodingOptions); - } + return null; } - public void UpdateEncoderPath(string path, string pathType) + private string GetEncoderPathFromDirectory(string path) { - if (_hasExternalEncoder) + try { - return; + var files = FileSystem.GetFilePaths(path); + + var excludeExtensions = new[] { ".c" }; + + return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); } - - _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); - - Tuple newPaths; - - if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase)) + catch (Exception) { - path = "ffmpeg"; - - newPaths = TestForInstalledVersions(); + // Trap all exceptions, like DirNotExists, and return null + return null; } - else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) - { - if (string.IsNullOrWhiteSpace(path)) - { - throw new ArgumentNullException(nameof(path)); - } - - if (!File.Exists(path) && !Directory.Exists(path)) - { - throw new ResourceNotFoundException(); - } - newPaths = GetEncoderPaths(path); - } - else - { - throw new ArgumentException("Unexpected pathType value"); - } - - if (string.IsNullOrWhiteSpace(newPaths.Item1)) - { - throw new ResourceNotFoundException("ffmpeg not found"); - } - if (string.IsNullOrWhiteSpace(newPaths.Item2)) - { - throw new ResourceNotFoundException("ffprobe not found"); - } - - path = newPaths.Item1; - - if (!ValidateVersion(path, true)) - { - throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required."); - } - - var config = GetEncodingOptions(); - config.EncoderAppPath = path; - ConfigurationManager.SaveConfiguration("encoding", config); - - Init(); - } - - private bool ValidateVersion(string path, bool logOutput) - { - return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput); - } - - private void ConfigureEncoderPaths() - { - if (_hasExternalEncoder) - { - return; - } - - var appPath = GetEncodingOptions().EncoderAppPath; - - if (string.IsNullOrWhiteSpace(appPath)) - { - appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg"); - } - - var newPaths = GetEncoderPaths(appPath); - if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath)) - { - newPaths = TestForInstalledVersions(); - } - - if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2)) - { - FFMpegPath = newPaths.Item1; - FFProbePath = newPaths.Item2; - } - - LogPaths(); - } - - private Tuple GetEncoderPaths(string configuredPath) - { - var appPath = configuredPath; - - if (!string.IsNullOrWhiteSpace(appPath)) - { - if (Directory.Exists(appPath)) - { - return GetPathsFromDirectory(appPath); - } - - if (File.Exists(appPath)) - { - return new Tuple(appPath, GetProbePathFromEncoderPath(appPath)); - } - } - - return new Tuple(null, null); - } - - private Tuple TestForInstalledVersions() - { - string encoderPath = null; - string probePath = null; - - if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true)) - { - encoderPath = _originalFFMpegPath; - probePath = _originalFFProbePath; - } - - if (string.IsNullOrWhiteSpace(encoderPath)) - { - if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false)) - { - encoderPath = "ffmpeg"; - probePath = "ffprobe"; - } - } - - return new Tuple(encoderPath, probePath); - } - - private Tuple GetPathsFromDirectory(string path) - { - // Since we can't predict the file extension, first try directly within the folder - // If that doesn't pan out, then do a recursive search - var files = FileSystem.GetFilePaths(path); - - var excludeExtensions = new[] { ".c" }; - - var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath)) - { - files = FileSystem.GetFilePaths(path, true); - - ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); - - if (!string.IsNullOrWhiteSpace(ffmpegPath)) - { - ffprobePath = GetProbePathFromEncoderPath(ffmpegPath); - } - } - - return new Tuple(ffmpegPath, ffprobePath); } private string GetProbePathFromEncoderPath(string appPath) @@ -357,15 +318,31 @@ namespace MediaBrowser.MediaEncoding.Encoder .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); } - private void LogPaths() + /// + /// Search the system $PATH environment variable looking for given filename. + /// + /// + /// + private string ExistsOnSystemPath(string fileName) { - _logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found"); - _logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found"); + var values = Environment.GetEnvironmentVariable("PATH"); + + foreach (var path in values.Split(Path.PathSeparator)) + { + var candidatePath = GetEncoderPathFromDirectory(path); + + if (ValidatePath("Found on PATH", candidatePath)) + { + return candidatePath; + } + } + return null; } - private EncodingOptions GetEncodingOptions() + private void LogPaths() { - return ConfigurationManager.GetConfiguration("encoding"); + _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found"); + _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found"); } private List _encoders = new List(); @@ -412,12 +389,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return true; } - /// - /// Gets the encoder path. - /// - /// The encoder path. - public string EncoderPath => FFMpegPath; - /// /// Gets the media info. /// @@ -489,7 +460,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, - FileName = FFProbePath, + FileName = FFprobePath, Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), IsHidden = true, @@ -691,7 +662,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFMpegPath, + FileName = FFmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, @@ -814,7 +785,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { CreateNoWindow = true, UseShellExecute = false, - FileName = FFMpegPath, + FileName = FFmpegPath, Arguments = args, IsHidden = true, ErrorDialog = false, diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 8584bd3ddb..ff697437ac 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -8,7 +8,8 @@ namespace MediaBrowser.Model.Configuration public bool EnableThrottling { get; set; } public int ThrottleDelaySeconds { get; set; } public string HardwareAccelerationType { get; set; } - public string EncoderAppPath { get; set; } + public string EncoderAppPathCustom { get; set; } // FFmpeg path as set by the user via the UI + public string EncoderAppPath { get; set; } // The current FFmpeg path being used by the system public string VaapiDevice { get; set; } public int H264Crf { get; set; } public string H264Preset { get; set; } From ed69e690b89e6a3e6e22bbf448af08a25c38e71b Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Tue, 12 Feb 2019 22:05:42 +0000 Subject: [PATCH 099/166] Review comments Address review comments from JustAMan, Bond-009 and cvium. --- .../ApplicationHost.cs | 35 +-- .../MediaEncoding/IMediaEncoder.cs | 3 +- .../Encoder/EncoderValidator.cs | 8 + .../Encoder/MediaEncoder.cs | 247 ++++++++---------- MediaBrowser.Model/System/SystemInfo.cs | 17 +- 5 files changed, 150 insertions(+), 160 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 2c0d0e7469..dd29f2adec 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -791,7 +791,17 @@ namespace Emby.Server.Implementations ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository); serviceCollection.AddSingleton(ChapterManager); - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000); + 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); @@ -908,27 +918,6 @@ namespace Emby.Server.Implementations return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder); } - /// - /// Registers the media encoder. - /// - /// Task. - private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo) - { - MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder( - LoggerFactory, - JsonSerializer, - StartupOptions.FFmpegPath, - StartupOptions.FFprobePath, - ServerConfigurationManager, - FileSystemManager, - () => SubtitleEncoder, - () => MediaSourceManager, - ProcessFactory, - 5000); - - RegisterSingleInstance(MediaEncoder); - } - /// /// Gets the user repository. /// @@ -1404,7 +1393,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 diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 057e439104..8852dac051 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -6,6 +6,7 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { @@ -14,7 +15,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// public interface IMediaEncoder : ITranscoderSupport { - string EncoderLocationType { get; } + FFmpegLocation EncoderLocation { get; } /// /// Gets the encoder path. diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 1eeea87a06..3eed891cb3 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -48,6 +48,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (string.IsNullOrWhiteSpace(output)) { + if (logOutput) + { + _logger.LogError("FFmpeg validation: The process returned no result"); + } return false; } @@ -55,6 +59,10 @@ namespace MediaBrowser.MediaEncoding.Encoder if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1) { + if (logOutput) + { + _logger.LogError("FFmpeg validation: avconv instead of ffmpeg is not supported"); + } return false; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 36d72cad90..9aad67ec70 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; @@ -18,6 +19,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; +using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Encoder @@ -34,17 +36,16 @@ namespace MediaBrowser.MediaEncoding.Encoder public string EncoderPath => FFmpegPath; /// - /// External: path supplied via command line - /// Custom: coming from UI or config/encoding.xml file - /// System: FFmpeg found in system $PATH - /// null: No FFmpeg found + /// The location of the discovered FFmpeg tool. /// - public string EncoderLocationType { get; private set; } + public FFmpegLocation EncoderLocation { get; private set; } + + private FFmpegLocation ProbeLocation; private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; - private string FFmpegPath { get; set; } - private string FFprobePath { get; set; } + private string FFmpegPath; + private string FFprobePath; protected readonly IServerConfigurationManager ConfigurationManager; protected readonly IFileSystem FileSystem; protected readonly Func SubtitleEncoder; @@ -54,6 +55,11 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string StartupOptionFFmpegPath; private readonly string StartupOptionFFprobePath; + /// + /// Enum to identify the two types of FF utilities of interest. + /// + private enum FFtype { Mpeg, Probe }; + private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); @@ -82,48 +88,24 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Run at startup or if the user removes a Custom path from transcode page. - /// Sets global variables FFmpegPath and EncoderLocationType. - /// If startup options --ffprobe is given then FFprobePath is set too. + /// Sets global variables FFmpegPath. + /// Precedence is: Config > CLI > $PATH /// public void Init() { - // 1) If given, use the --ffmpeg CLI switch - if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath)) + // 1) Custom path stored in config/encoding xml file under tag takes precedence + if (!ValidatePath(FFtype.Mpeg, ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom, FFmpegLocation.Custom)) { - _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg"); - EncoderLocationType = "External"; - } - - // 2) Try Custom path stroed in config/encoding xml file under tag - else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom)) - { - _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file"); - EncoderLocationType = "Custom"; - } - - // 3) Search system $PATH environment variable for valid FFmpeg - else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg"))) - { - _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg"); - EncoderLocationType = "System"; - } - else - { - _logger.LogError("FFmpeg: No suitable executable found"); - FFmpegPath = null; - EncoderLocationType = null; - } - - // If given, use the --ffprobe CLI switch - if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath)) - { - _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe"); - } - else - { - // FFprobe path from command line is no good, so set to null and let ReInit() try - // and set using the FFmpeg path. - FFprobePath = null; + // 2) Check if the --ffmpeg CLI switch has been given + if (!ValidatePath(FFtype.Mpeg, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + { + // 3) Search system $PATH environment variable for valid FFmpeg + if (!ValidatePath(FFtype.Mpeg, ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) + { + EncoderLocation = FFmpegLocation.NotFound; + FFmpegPath = null; + } + } } ReInit(); @@ -136,27 +118,27 @@ namespace MediaBrowser.MediaEncoding.Encoder /// private void ReInit() { - // Write the FFmpeg path to the config/encoding.xml file so it appears in UI + // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI var config = ConfigurationManager.GetConfiguration("encoding"); config.EncoderAppPath = FFmpegPath ?? string.Empty; ConfigurationManager.SaveConfiguration("encoding", config); + // Clear probe settings in case probe validation fails + ProbeLocation = FFmpegLocation.NotFound; + FFprobePath = null; + // Only if mpeg path is set, try and set path to probe if (FFmpegPath != null) { - // Probe would be null here if no valid --ffprobe path was given - // at startup, or we're performing ReInit following mpeg path update from UI - if (FFprobePath == null) + if (EncoderLocation == FFmpegLocation.Custom || StartupOptionFFprobePath == null) { - // Use the mpeg path to create a probe path - if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath))) - { - _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg"); - } - else - { - _logger.LogError("FFprobe: No suitable executable found"); - } + // If mpeg was read from config, or CLI switch not given, try and set probe from mpeg path + ValidatePath(FFtype.Probe, GetProbePathFromEncoderPath(FFmpegPath), EncoderLocation); + } + else + { + // Else try and set probe path from CLI switch + ValidatePath(FFtype.Probe, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument); } // Interrogate to understand what coders it supports @@ -183,108 +165,95 @@ namespace MediaBrowser.MediaEncoding.Encoder { throw new ArgumentException("Unexpected pathType value"); } + + if (string.IsNullOrWhiteSpace(path)) + { + // User had cleared the custom path in UI. Clear the Custom config + // setting and perform full Init to reinspect any CLI switches and system $PATH + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = string.Empty; + ConfigurationManager.SaveConfiguration("encoding", config); + + Init(); + } + else if (!File.Exists(path) && !Directory.Exists(path)) + { + // Given path is neither file or folder + throw new ResourceNotFoundException(); + } else { - if (string.IsNullOrWhiteSpace(path)) + // Supplied path could be either file path or folder path. + // Resolve down to file path and validate + if (!ValidatePath(FFtype.Mpeg, GetEncoderPath(path), FFmpegLocation.Custom)) { - // User had cleared the cutom path in UI. Clear the Custom config - // setting and peform full Init to relook any CLI switches and system $PATH - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = string.Empty; - ConfigurationManager.SaveConfiguration("encoding", config); - - Init(); - } - else if (!File.Exists(path) && !Directory.Exists(path)) - { - // Given path is neither file or folder - throw new ResourceNotFoundException(); + throw new ResourceNotFoundException("Failed validation checks."); } else { - // Supplied path could be either file path or folder path. - // Resolve down to file path and validate - path = GetEncoderPath(path); + // Write the validated mpeg path to the xml as + // This ensures its not lost on new startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPathCustom = FFmpegPath; + ConfigurationManager.SaveConfiguration("encoding", config); - if (path == null) - { - throw new ResourceNotFoundException("FFmpeg not found"); - } - else if (!ValidatePathFFmpeg("New From UI", path)) - { - throw new ResourceNotFoundException("Failed validation checks. Version 4.0 or greater is required"); - } - else - { - EncoderLocationType = "Custom"; - - // Write the validated mpeg path to the xml as - // This ensures its not lost on new startup - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = FFmpegPath; - ConfigurationManager.SaveConfiguration("encoding", config); - - FFprobePath = null; // Clear probe path so it gets relooked in ReInit() - - ReInit(); - } + ReInit(); } } } - private bool ValidatePath(string type, string path) + /// + /// Validates the supplied FQPN to ensure it is a FFxxx utility. + /// If checks pass, global variable FFmpegPath (or FFprobePath) and + /// EncoderLocation (or ProbeLocation) are updated. + /// + /// Either mpeg or probe + /// FQPN to test + /// Location (External, Custom, System) of tool + /// + private bool ValidatePath(FFtype type, string path, FFmpegLocation location) { + bool rc = false; + if (!string.IsNullOrEmpty(path)) { if (File.Exists(path)) { - var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); + rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, false); - if (valid == true) + // Only update the global variables if the checks passed + if (rc) { - return true; + if (type == FFtype.Mpeg) + { + FFmpegPath = path; + EncoderLocation = location; + } + else + { + FFprobePath = path; + ProbeLocation = location; + } } else { - _logger.LogError("{0}: Failed validation checks. Version 4.0 or greater is required: {1}", type, path); + _logger.LogError("{0}: {1}: Failed version check: {2}", type.ToString(), location.ToString(), path); } } else { - _logger.LogError("{0}: File not found: {1}", type, path); + _logger.LogError("{0}: {1}: File not found: {2}", type.ToString(), location.ToString(), path); } } - return false; - } - - private bool ValidatePathFFmpeg(string comment, string path) - { - if (ValidatePath("FFmpeg: " + comment, path) == true) - { - FFmpegPath = path; - return true; - } - - return false; - } - - private bool ValidatePathFFprobe(string comment, string path) - { - if (ValidatePath("FFprobe: " + comment, path) == true) - { - FFprobePath = path; - return true; - } - - return false; + return rc; } private string GetEncoderPath(string path) { if (Directory.Exists(path)) { - return GetEncoderPathFromDirectory(path); + return GetEncoderPathFromDirectory(path, "ffmpeg"); } if (File.Exists(path)) @@ -295,7 +264,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return null; } - private string GetEncoderPathFromDirectory(string path) + private string GetEncoderPathFromDirectory(string path, string filename) { try { @@ -303,7 +272,8 @@ namespace MediaBrowser.MediaEncoding.Encoder var excludeExtensions = new[] { ".c" }; - return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); + return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), filename, StringComparison.OrdinalIgnoreCase) + && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty)); } catch (Exception) { @@ -314,8 +284,15 @@ namespace MediaBrowser.MediaEncoding.Encoder private string GetProbePathFromEncoderPath(string appPath) { - return FileSystem.GetFilePaths(Path.GetDirectoryName(appPath)) - .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrEmpty(appPath)) + { + string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$"; + string substitution = @"ffprobe$1"; + + return Regex.Replace(appPath, pattern, substitution); + } + + return null; } /// @@ -323,15 +300,15 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// /// - private string ExistsOnSystemPath(string fileName) + private string ExistsOnSystemPath(string filename) { var values = Environment.GetEnvironmentVariable("PATH"); foreach (var path in values.Split(Path.PathSeparator)) { - var candidatePath = GetEncoderPathFromDirectory(path); + var candidatePath = GetEncoderPathFromDirectory(path, filename); - if (ValidatePath("Found on PATH", candidatePath)) + if (!string.IsNullOrEmpty(candidatePath)) { return candidatePath; } @@ -341,8 +318,8 @@ namespace MediaBrowser.MediaEncoding.Encoder private void LogPaths() { - _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found"); - _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found"); + _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty); + _logger.LogInformation("FFprobe: {0}: {1}", ProbeLocation.ToString(), FFprobePath ?? string.Empty); } private List _encoders = new List(); diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 581a1069cd..6482f2c840 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -4,6 +4,21 @@ using MediaBrowser.Model.Updates; namespace MediaBrowser.Model.System { + /// + /// Enum describing the location of the FFmpeg tool. + /// + public enum FFmpegLocation + { + /// No path to FFmpeg found. + NotFound, + /// Path supplied via command line using switch --ffmpeg. + SetByArgument, + /// User has supplied path via Transcoding UI page. + Custom, + /// FFmpeg tool found on system $PATH. + System + }; + /// /// Class SystemInfo /// @@ -122,7 +137,7 @@ namespace MediaBrowser.Model.System /// true if this instance has update available; otherwise, false. public bool HasUpdateAvailable { get; set; } - public string EncoderLocationType { get; set; } + public FFmpegLocation EncoderLocation { get; set; } public Architecture SystemArchitecture { get; set; } From 632968dc817a398bff84ca3ef82337b4e5786416 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Wed, 13 Feb 2019 16:46:03 +0000 Subject: [PATCH 100/166] Added self to contributors --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 4b397b3280..81857e57c7 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,6 +23,7 @@ - [fruhnow](https://github.com/fruhnow) - [Lynxy](https://github.com/Lynxy) - [fasheng](https://github.com/fasheng) + - [ploughpuff](https://github.com/ploughpuff) # Emby Contributors From 8104e739d5b83d0d6563bb685f910697e60d6c12 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Wed, 27 Feb 2019 16:33:08 +0000 Subject: [PATCH 101/166] Address review comments from Bond --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 10 ++++++++-- MediaBrowser.Model/Configuration/EncodingOptions.cs | 10 ++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 9aad67ec70..265c043b98 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -282,12 +282,18 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + /// + /// With the given path string, replaces the filename with ffprobe, taking case + /// of any file extension (like .exe on windows). + /// + /// + /// private string GetProbePathFromEncoderPath(string appPath) { if (!string.IsNullOrEmpty(appPath)) { - string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$"; - string substitution = @"ffprobe$1"; + const string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$"; + const string substitution = @"ffprobe$1"; return Regex.Replace(appPath, pattern, substitution); } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index ff697437ac..7fc985ba02 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -8,8 +8,14 @@ namespace MediaBrowser.Model.Configuration public bool EnableThrottling { get; set; } public int ThrottleDelaySeconds { get; set; } public string HardwareAccelerationType { get; set; } - public string EncoderAppPathCustom { get; set; } // FFmpeg path as set by the user via the UI - public string EncoderAppPath { get; set; } // The current FFmpeg path being used by the system + /// + /// FFmpeg path as set by the user via the UI + /// + public string EncoderAppPathCustom { get; set; } + /// + /// The current FFmpeg path being used by the system + /// + public string EncoderAppPath { get; set; } public string VaapiDevice { get; set; } public int H264Crf { get; set; } public string H264Preset { get; set; } From 656bffbbb291dc9b58443bb36c8ed7d3688f6c1b Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 28 Feb 2019 22:06:56 +0000 Subject: [PATCH 102/166] Remove --ffprobe logic --- Jellyfin.Server/StartupOptions.cs | 4 +- .../Encoder/MediaEncoder.cs | 185 +++++------------- .../Configuration/EncodingOptions.cs | 8 +- 3 files changed, 59 insertions(+), 138 deletions(-) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 5d3f7b171b..c8cdb984db 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -20,10 +20,10 @@ namespace Jellyfin.Server [Option('l', "logdir", Required = false, HelpText = "Path to use for writing log files.")] public string LogDir { get; set; } - [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH. Must be specified along with --ffprobe.")] + [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")] public string FFmpegPath { get; set; } - [Option("ffprobe", Required = false, HelpText = "Path to external FFprobe executable to use in place of default found in PATH. Must be specified along with --ffmpeg.")] + [Option("ffprobe", Required = false, HelpText = "(deprecated) Option has no effect and shall be removed in next release.")] public string FFprobePath { get; set; } [Option("service", Required = false, HelpText = "Run as headless service.")] diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 265c043b98..51b4f6e393 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -40,8 +40,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public FFmpegLocation EncoderLocation { get; private set; } - private FFmpegLocation ProbeLocation; - private readonly ILogger _logger; private readonly IJsonSerializer _jsonSerializer; private string FFmpegPath; @@ -55,11 +53,6 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string StartupOptionFFmpegPath; private readonly string StartupOptionFFprobePath; - /// - /// Enum to identify the two types of FF utilities of interest. - /// - private enum FFtype { Mpeg, Probe }; - private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); @@ -93,14 +86,20 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public void Init() { - // 1) Custom path stored in config/encoding xml file under tag takes precedence - if (!ValidatePath(FFtype.Mpeg, ConfigurationManager.GetConfiguration("encoding").EncoderAppPathCustom, FFmpegLocation.Custom)) + // ToDo - Finalise removal of the --ffprobe switch + if (!string.IsNullOrEmpty(StartupOptionFFprobePath)) + { + _logger.LogWarning("--ffprobe switch is deprecated and shall be removed in the next release"); + } + + // 1) Custom path stored in config/encoding xml file under tag takes precedence + if (!ValidatePath(ConfigurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) { // 2) Check if the --ffmpeg CLI switch has been given - if (!ValidatePath(FFtype.Mpeg, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument)) { // 3) Search system $PATH environment variable for valid FFmpeg - if (!ValidatePath(FFtype.Mpeg, ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) + if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) { EncoderLocation = FFmpegLocation.NotFound; FFmpegPath = null; @@ -108,110 +107,80 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - ReInit(); - } - - /// - /// Writes the currently used FFmpeg to config/encoding.xml file. - /// Sets the FFprobe path if not currently set. - /// Interrogates the FFmpeg tool to identify what encoders/decodres are available. - /// - private void ReInit() - { - // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI + // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPath = FFmpegPath ?? string.Empty; + config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty; ConfigurationManager.SaveConfiguration("encoding", config); - // Clear probe settings in case probe validation fails - ProbeLocation = FFmpegLocation.NotFound; - FFprobePath = null; - // Only if mpeg path is set, try and set path to probe if (FFmpegPath != null) { - if (EncoderLocation == FFmpegLocation.Custom || StartupOptionFFprobePath == null) - { - // If mpeg was read from config, or CLI switch not given, try and set probe from mpeg path - ValidatePath(FFtype.Probe, GetProbePathFromEncoderPath(FFmpegPath), EncoderLocation); - } - else - { - // Else try and set probe path from CLI switch - ValidatePath(FFtype.Probe, StartupOptionFFmpegPath, FFmpegLocation.SetByArgument); - } + // Determine a probe path from the mpeg path + FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1"); - // Interrogate to understand what coders it supports + // Interrogate to understand what coders are supported var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath); SetAvailableDecoders(result.decoders); SetAvailableEncoders(result.encoders); } - // Stamp FFmpeg paths to the log file - LogPaths(); + _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty); } /// - /// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use. + /// Triggered from the Settings > Transcoding UI page when users submits Custom FFmpeg path to use. + /// Only write the new path to xml if it exists. Do not perform validation checks on ffmpeg here. /// /// /// public void UpdateEncoderPath(string path, string pathType) { + string newPath; + _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty); if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Unexpected pathType value"); } - - if (string.IsNullOrWhiteSpace(path)) + else if (string.IsNullOrWhiteSpace(path)) { - // User had cleared the custom path in UI. Clear the Custom config - // setting and perform full Init to reinspect any CLI switches and system $PATH - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = string.Empty; - ConfigurationManager.SaveConfiguration("encoding", config); - - Init(); + // User had cleared the custom path in UI + newPath = string.Empty; } - else if (!File.Exists(path) && !Directory.Exists(path)) + else if (File.Exists(path)) { - // Given path is neither file or folder - throw new ResourceNotFoundException(); + newPath = path; + } + else if (Directory.Exists(path)) + { + // Given path is directory, so resolve down to filename + newPath = GetEncoderPathFromDirectory(path, "ffmpeg"); } else { - // Supplied path could be either file path or folder path. - // Resolve down to file path and validate - if (!ValidatePath(FFtype.Mpeg, GetEncoderPath(path), FFmpegLocation.Custom)) - { - throw new ResourceNotFoundException("Failed validation checks."); - } - else - { - // Write the validated mpeg path to the xml as - // This ensures its not lost on new startup - var config = ConfigurationManager.GetConfiguration("encoding"); - config.EncoderAppPathCustom = FFmpegPath; - ConfigurationManager.SaveConfiguration("encoding", config); - - ReInit(); - } + throw new ResourceNotFoundException(); } + + // Write the new ffmpeg path to the xml as + // This ensures its not lost on next startup + var config = ConfigurationManager.GetConfiguration("encoding"); + config.EncoderAppPath = newPath; + ConfigurationManager.SaveConfiguration("encoding", config); + + // Trigger Init so we validate the new path and setup probe path + Init(); } /// - /// Validates the supplied FQPN to ensure it is a FFxxx utility. - /// If checks pass, global variable FFmpegPath (or FFprobePath) and - /// EncoderLocation (or ProbeLocation) are updated. + /// Validates the supplied FQPN to ensure it is a ffmpeg utility. + /// If checks pass, global variable FFmpegPath and EncoderLocation are updated. /// - /// Either mpeg or probe /// FQPN to test /// Location (External, Custom, System) of tool /// - private bool ValidatePath(FFtype type, string path, FFmpegLocation location) + private bool ValidatePath(string path, FFmpegLocation location) { bool rc = false; @@ -219,51 +188,28 @@ namespace MediaBrowser.MediaEncoding.Encoder { if (File.Exists(path)) { - rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, false); + rc = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true); - // Only update the global variables if the checks passed - if (rc) + if (!rc) { - if (type == FFtype.Mpeg) - { - FFmpegPath = path; - EncoderLocation = location; - } - else - { - FFprobePath = path; - ProbeLocation = location; - } - } - else - { - _logger.LogError("{0}: {1}: Failed version check: {2}", type.ToString(), location.ToString(), path); + _logger.LogWarning("FFmpeg: {0}: Failed version check: {1}", location.ToString(), path); } + + // ToDo - Enable the ffmpeg validator. At the moment any version can be used. + rc = true; + + FFmpegPath = path; + EncoderLocation = location; } else { - _logger.LogError("{0}: {1}: File not found: {2}", type.ToString(), location.ToString(), path); + _logger.LogWarning("FFmpeg: {0}: File not found: {1}", location.ToString(), path); } } return rc; } - private string GetEncoderPath(string path) - { - if (Directory.Exists(path)) - { - return GetEncoderPathFromDirectory(path, "ffmpeg"); - } - - if (File.Exists(path)) - { - return path; - } - - return null; - } - private string GetEncoderPathFromDirectory(string path, string filename) { try @@ -282,25 +228,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - /// - /// With the given path string, replaces the filename with ffprobe, taking case - /// of any file extension (like .exe on windows). - /// - /// - /// - private string GetProbePathFromEncoderPath(string appPath) - { - if (!string.IsNullOrEmpty(appPath)) - { - const string pattern = @"[^\/\\]+?(\.[^\/\\\n.]+)?$"; - const string substitution = @"ffprobe$1"; - - return Regex.Replace(appPath, pattern, substitution); - } - - return null; - } - /// /// Search the system $PATH environment variable looking for given filename. /// @@ -322,12 +249,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return null; } - private void LogPaths() - { - _logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation.ToString(), FFmpegPath ?? string.Empty); - _logger.LogInformation("FFprobe: {0}: {1}", ProbeLocation.ToString(), FFprobePath ?? string.Empty); - } - private List _encoders = new List(); public void SetAvailableEncoders(IEnumerable list) { diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 7fc985ba02..285ff4ba58 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -11,11 +11,11 @@ namespace MediaBrowser.Model.Configuration /// /// FFmpeg path as set by the user via the UI /// - public string EncoderAppPathCustom { get; set; } - /// - /// The current FFmpeg path being used by the system - /// public string EncoderAppPath { get; set; } + /// + /// The current FFmpeg path being used by the system and displayed on the transcode page + /// + public string EncoderAppPathDisplay { get; set; } public string VaapiDevice { get; set; } public int H264Crf { get; set; } public string H264Preset { get; set; } From 2617a49b78c99f72ba36e53a4c97c4e042116a53 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 28 Feb 2019 22:47:56 +0000 Subject: [PATCH 103/166] Renamed Init() to SetFFmpegPath() --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index dd29f2adec..325df32931 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -534,7 +534,7 @@ namespace Emby.Server.Implementations ConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated; - MediaEncoder.Init(); + MediaEncoder.SetFFmpegPath(); //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) //{ diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 8852dac051..d4ac3b7c37 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -92,7 +92,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. string EscapeSubtitleFilterPath(string path); - void Init(); + void SetFFmpegPath(); void UpdateEncoderPath(string path, string pathType); bool SupportsEncoder(string encoder); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 51b4f6e393..2924577888 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -84,7 +84,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// Sets global variables FFmpegPath. /// Precedence is: Config > CLI > $PATH /// - public void Init() + public void SetFFmpegPath() { // ToDo - Finalise removal of the --ffprobe switch if (!string.IsNullOrEmpty(StartupOptionFFprobePath)) @@ -169,8 +169,8 @@ namespace MediaBrowser.MediaEncoding.Encoder config.EncoderAppPath = newPath; ConfigurationManager.SaveConfiguration("encoding", config); - // Trigger Init so we validate the new path and setup probe path - Init(); + // Trigger SetFFmpegPath so we validate the new path and setup probe path + SetFFmpegPath(); } /// From bef665be364ce1477d09ed268f68c19e0099922f Mon Sep 17 00:00:00 2001 From: Phallacy Date: Tue, 5 Mar 2019 23:45:05 -0800 Subject: [PATCH 104/166] Minor fixes to address style issues --- .../Library/DefaultAuthenticationProvider.cs | 14 +++++++------- .../Library/UserManager.cs | 15 +++++---------- MediaBrowser.Model/Cryptography/PasswordHash.cs | 7 ++++--- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 8f10b5a84e..3ac604b40e 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -50,22 +50,22 @@ namespace Emby.Server.Implementations.Library byte[] passwordbytes = Encoding.UTF8.GetBytes(password); PasswordHash readyHash = new PasswordHash(resolvedUser.Password); - byte[] CalculatedHash; - string CalculatedHashString; + 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); + 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); + calculatedHash = _cryptographyProvider.ComputeHash(readyHash.Id, passwordbytes, readyHash.SaltBytes); + calculatedHashString = BitConverter.ToString(calculatedHash).Replace("-", string.Empty); } - if (CalculatedHashString == readyHash.Hash) + if (calculatedHashString == readyHash.Hash) { success = true; // throw new Exception("Invalid username or password"); diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 57bf16364d..efb1ef4a50 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -475,11 +475,6 @@ namespace Emby.Server.Implementations.Library : user.EasyPassword; } - private bool IsPasswordEmpty(User user, string passwordHash) - { - return string.IsNullOrEmpty(passwordHash); - } - /// /// Loads the users from the repository /// @@ -522,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, @@ -548,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) { diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index a528404045..7a1be833db 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -100,13 +100,14 @@ namespace MediaBrowser.Model.Cryptography public static byte[] ConvertFromByteString(string byteString) { - List Bytes = new List(); + List bytes = new List(); for (int i = 0; i < byteString.Length; i += 2) { - Bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); + // TODO: NetStandard2.1 switch this to use a span instead of a substring. + bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); } - return Bytes.ToArray(); + return bytes.ToArray(); } public static string ConvertToByteString(byte[] bytes) From 04db0369d46c4b3961086b1d30946bd990a5767c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 6 Mar 2019 17:31:52 +0100 Subject: [PATCH 105/166] Update LocalizationManager.cs --- .../Localization/LocalizationManager.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index d55b258a3d..762649b716 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -93,12 +93,11 @@ namespace Emby.Server.Implementations.Localization #if DEBUG else { - _logger.LogWarning("Misformed line in ratings file for country {CountryCode}", countryCode); + _logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); } #endif } } - _logger.LogWarning("{t}", countryCode); _allParentalRatings[countryCode] = dict; } From 394d23a73a530b5ce2609d01575e0ced734aecfe Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:14:03 +0100 Subject: [PATCH 106/166] Review comments --- Emby.Server.Implementations/WebSockets/WebSocketManager.cs | 2 +- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs index 5db2c8da73..04c73ecea7 100644 --- a/Emby.Server.Implementations/WebSockets/WebSocketManager.cs +++ b/Emby.Server.Implementations/WebSockets/WebSocketManager.cs @@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.WebSockets ? Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length) : Encoding.ASCII.GetString(messageBytes, 0, messageBytes.Length); - // All messages are expected to be json + // 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); diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index fc81e532d8..399ac2be09 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -362,6 +362,7 @@ namespace MediaBrowser.Api.Playback.Progressive var contentType = state.GetMimeType(outputPath); + // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response // Headers only if (isHeadRequest) { From 21c2acc520bcebe74dacc4caf1c244e258a12aec Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:27:05 +0100 Subject: [PATCH 107/166] Remove public Host property --- Emby.Server.Implementations/ApplicationHost.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 93147539c6..71a112dacf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -129,7 +129,7 @@ namespace Emby.Server.Implementations /// /// true if this instance can self restart; otherwise, false. public abstract bool CanSelfRestart { get; } - public IWebHost Host { get; set; } + public virtual bool CanLaunchWebBrowser { get @@ -625,7 +625,7 @@ namespace Emby.Server.Implementations contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); } - Host = new WebHostBuilder() + var host = new WebHostBuilder() .UseKestrel(options => { options.ListenAnyIP(HttpPort); @@ -652,10 +652,10 @@ namespace Emby.Server.Implementations }) .Build(); - await Host.StartAsync(); + await host.StartAsync(); } - public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) + private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -665,7 +665,8 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + + private async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { if (context.WebSockets.IsWebSocketRequest) { From bba049c987a632375da5e847512efe7ec4a85a31 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Wed, 6 Mar 2019 19:29:25 +0100 Subject: [PATCH 108/166] Make FileSystem readonly --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 78e2015b1e..0e989994b1 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -17,7 +17,7 @@ namespace Emby.Server.Implementations.HttpServer { private readonly IStreamHelper _streamHelper; private ILogger Logger { get; set; } - public IFileSystem FileSystem { get; } + private readonly IFileSystem _fileSystem; private string RangeHeader { get; set; } private bool IsHeadRequest { get; set; } @@ -54,10 +54,10 @@ namespace Emby.Server.Implementations.HttpServer } _streamHelper = streamHelper; + _fileSystem = fileSystem; Path = path; Logger = logger; - FileSystem = fileSystem; RangeHeader = rangeHeader; Headers[HeaderNames.ContentType] = contentType; @@ -181,7 +181,7 @@ namespace Emby.Server.Implementations.HttpServer count = 0; } - await response.TransmitFile(path, offset, count, FileShare, FileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); + await response.TransmitFile(path, offset, count, FileShare, _fileSystem, _streamHelper, cancellationToken).ConfigureAwait(false); } finally { From c31b0b311b339475650aa8812eb57152cac32d80 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 02:41:44 -0800 Subject: [PATCH 109/166] Apply suggestions from code review more minor fixes before I do larger fixes Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Cryptography/CryptographyProvider.cs | 6 +- .../Library/DefaultAuthenticationProvider.cs | 2 +- .../Cryptography/PasswordHash.cs | 132 +++++++++--------- 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs index cf1ea6efa3..2e882cefed 100644 --- a/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs +++ b/Emby.Server.Implementations/Cryptography/CryptographyProvider.cs @@ -83,7 +83,7 @@ namespace Emby.Server.Implementations.Cryptography { //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) + if (method == DefaultHashMethod) { using (var r = new Rfc2898DeriveBytes(bytes, salt, iterations)) { @@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string hashMethod, byte[] bytes) { - return ComputeHash(hashMethod, bytes, new byte[0]); + return ComputeHash(hashMethod, bytes, Array.Empty()); } public byte[] ComputeHashWithDefaultMethod(byte[] bytes) @@ -106,7 +106,7 @@ namespace Emby.Server.Implementations.Cryptography public byte[] ComputeHash(string hashMethod, byte[] bytes, byte[] salt) { - if(hashMethod == DefaultHashMethod) + if (hashMethod == DefaultHashMethod) { return PBKDF2(hashMethod, bytes, salt, _defaultIterations); } diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3ac604b40e..526509f439 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.Library if (!user.Password.Contains("$")) { string hash = user.Password; - user.Password = String.Format("$SHA1${0}", hash); + user.Password = string.Format("$SHA1${0}", hash); } if (user.EasyPassword != null && !user.EasyPassword.Contains("$")) diff --git a/MediaBrowser.Model/Cryptography/PasswordHash.cs b/MediaBrowser.Model/Cryptography/PasswordHash.cs index 7a1be833db..72bdc6745e 100644 --- a/MediaBrowser.Model/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Model/Cryptography/PasswordHash.cs @@ -8,34 +8,34 @@ namespace MediaBrowser.Model.Cryptography { // Defined from this hash storage spec // https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md - // $[$=(,=)*][$[$]] - // with one slight amendment to ease the transition, we're writing out the bytes in hex + // $[$=(,=)*][$[$]] + // with one slight amendment to ease the transition, we're writing out the bytes in hex // rather than making them a BASE64 string with stripped padding - private string _id; + private string _id; - private Dictionary _parameters = new Dictionary(); + private Dictionary _parameters = new Dictionary(); - private string _salt; + private string _salt; - private byte[] _saltBytes; + private byte[] _saltBytes; - private string _hash; + private string _hash; + + private byte[] _hashBytes; + + public string Id { get => _id; set => _id = value; } + + public Dictionary Parameters { get => _parameters; set => _parameters = value; } + + public string Salt { get => _salt; set => _salt = value; } + + public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } + + public string Hash { get => _hash; set => _hash = value; } + + public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } - private byte[] _hashBytes; - - public string Id { get => _id; set => _id = value; } - - public Dictionary Parameters { get => _parameters; set => _parameters = value; } - - public string Salt { get => _salt; set => _salt = value; } - - public byte[] SaltBytes { get => _saltBytes; set => _saltBytes = value; } - - public string Hash { get => _hash; set => _hash = value; } - - public byte[] HashBytes { get => _hashBytes; set => _hashBytes = value; } - public PasswordHash(string storageString) { string[] splitted = storageString.Split('$'); @@ -46,14 +46,14 @@ namespace MediaBrowser.Model.Cryptography { if (!string.IsNullOrEmpty(paramset)) { - string[] fields = paramset.Split('='); - if (fields.Length == 2) - { - _parameters.Add(fields[0], fields[1]); - } - else - { - throw new Exception($"Malformed parameter in password hash string {paramset}"); + string[] fields = paramset.Split('='); + if (fields.Length == 2) + { + _parameters.Add(fields[0], fields[1]); + } + else + { + throw new Exception($"Malformed parameter in password hash string {paramset}"); } } } @@ -89,31 +89,31 @@ namespace MediaBrowser.Model.Cryptography } - } - + } + public PasswordHash(ICryptoProvider cryptoProvider) { _id = cryptoProvider.DefaultHashMethod; _saltBytes = cryptoProvider.GenerateSalt(); - _salt = ConvertToByteString(SaltBytes); - } - - public static byte[] ConvertFromByteString(string byteString) - { - List bytes = new List(); - for (int i = 0; i < byteString.Length; i += 2) - { - // TODO: NetStandard2.1 switch this to use a span instead of a substring. - bytes.Add(Convert.ToByte(byteString.Substring(i, 2),16)); - } - - return bytes.ToArray(); - } - - public static string ConvertToByteString(byte[] bytes) - { - return BitConverter.ToString(bytes).Replace("-", ""); - } + _salt = ConvertToByteString(SaltBytes); + } + + public static byte[] ConvertFromByteString(string byteString) + { + List bytes = new List(); + for (int i = 0; i < byteString.Length; i += 2) + { + // TODO: NetStandard2.1 switch this to use a span instead of a substring. + bytes.Add(Convert.ToByte(byteString.Substring(i, 2), 16)); + } + + return bytes.ToArray(); + } + + public static string ConvertToByteString(byte[] bytes) + { + return BitConverter.ToString(bytes).Replace("-", ""); + } private string SerializeParameters() { @@ -121,33 +121,33 @@ namespace MediaBrowser.Model.Cryptography foreach (var KVP in _parameters) { ReturnString += $",{KVP.Key}={KVP.Value}"; - } + } if ((!string.IsNullOrEmpty(ReturnString)) && ReturnString[0] == ',') { ReturnString = ReturnString.Remove(0, 1); - } + } return ReturnString; } public override string ToString() - { - string outString = "$" +_id; - string paramstring = SerializeParameters(); - if (!string.IsNullOrEmpty(paramstring)) - { - outString += $"${paramstring}"; - } - - if (!string.IsNullOrEmpty(_salt)) - { - outString += $"${_salt}"; - } - + { + string outString = "$" + _id; + string paramstring = SerializeParameters(); + if (!string.IsNullOrEmpty(paramstring)) + { + outString += $"${paramstring}"; + } + + if (!string.IsNullOrEmpty(_salt)) + { + outString += $"${_salt}"; + } + outString += $"${_hash}"; return outString; } } -} +} From a9302b8b53e8393fbedeefb3be75ae6eba8ae815 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 1 Feb 2019 17:43:31 +0100 Subject: [PATCH 110/166] Remove useless abstraction around XmlReaderSettings This removes the amount of stuff that needs to be passed around Also removes some unneeded `ManagedFileSystem` usage --- .../ConnectionManager/ConnectionManager.cs | 7 +- Emby.Dlna/ConnectionManager/ControlHandler.cs | 4 +- .../ContentDirectory/ContentDirectory.cs | 11 +- Emby.Dlna/ContentDirectory/ControlHandler.cs | 19 +- Emby.Dlna/Main/DlnaEntryPoint.cs | 15 +- .../MediaReceiverRegistrar/ControlHandler.cs | 5 +- .../MediaReceiverRegistrar.cs | 7 +- Emby.Dlna/Service/BaseControlHandler.cs | 17 +- .../ApplicationHost.cs | 4 - .../Xml/XmlReaderSettingsFactory.cs | 20 - .../Parsers/BaseItemXmlParser.cs | 20 +- .../Parsers/BoxSetXmlParser.cs | 5 +- .../Parsers/PlaylistXmlParser.cs | 5 +- .../Providers/BoxSetXmlProvider.cs | 7 +- .../Providers/PlaylistXmlProvider.cs | 7 +- .../Savers/BaseXmlSaver.cs | 7 +- .../Savers/BoxSetXmlSaver.cs | 4 +- .../Savers/PlaylistXmlSaver.cs | 4 +- .../Xml/IXmlReaderSettingsFactory.cs | 9 - .../Music/MusicBrainzAlbumProvider.cs | 175 ++++---- .../Music/MusicBrainzArtistProvider.cs | 19 +- .../TV/MissingEpisodeProvider.cs | 3 - .../TV/SeriesMetadataService.cs | 4 - .../TV/TheTVDB/TvdbPrescanTask.cs | 396 ++++++++++++++++++ .../TV/TheTVDB/TvdbSeriesProvider.cs | 1 - .../Parsers/BaseNfoParser.cs | 34 +- .../Parsers/EpisodeNfoParser.cs | 5 +- .../Parsers/MovieNfoParser.cs | 19 +- .../Parsers/SeasonNfoParser.cs | 5 +- .../Parsers/SeriesNfoParser.cs | 5 +- .../Providers/AlbumNfoProvider.cs | 7 +- .../Providers/ArtistNfoProvider.cs | 7 +- .../Providers/BaseVideoNfoProvider.cs | 7 +- .../Providers/EpisodeNfoProvider.cs | 7 +- .../Providers/MovieNfoProvider.cs | 10 +- .../Providers/SeasonNfoProvider.cs | 11 +- .../Providers/SeriesNfoProvider.cs | 13 +- .../Savers/AlbumNfoSaver.cs | 4 +- .../Savers/ArtistNfoSaver.cs | 4 +- .../Savers/BaseNfoSaver.cs | 87 ++-- .../Savers/EpisodeNfoSaver.cs | 4 +- .../Savers/MovieNfoSaver.cs | 4 +- .../Savers/SeasonNfoSaver.cs | 6 +- .../Savers/SeriesNfoSaver.cs | 4 +- 44 files changed, 654 insertions(+), 364 deletions(-) delete mode 100644 Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs delete mode 100644 MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs create mode 100644 MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs index cc427f2a15..679eecc891 100644 --- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs +++ b/Emby.Dlna/ConnectionManager/ConnectionManager.cs @@ -3,7 +3,6 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ConnectionManager @@ -13,15 +12,13 @@ namespace Emby.Dlna.ConnectionManager private readonly IDlnaManager _dlna; private readonly ILogger _logger; private readonly IServerConfigurationManager _config; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient) : base(logger, httpClient) { _dlna = dlna; _config = config; _logger = logger; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public string GetServiceXml(IDictionary headers) @@ -34,7 +31,7 @@ namespace Emby.Dlna.ConnectionManager var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile(); - return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request); + return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request); } } } diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs index 16211c61f4..2e11047487 100644 --- a/Emby.Dlna/ConnectionManager/ControlHandler.cs +++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs @@ -4,7 +4,6 @@ using Emby.Dlna.Service; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ConnectionManager @@ -32,7 +31,8 @@ namespace Emby.Dlna.ConnectionManager }; } - public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile) + : base(config, logger) { _profile = profile; } diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs index b0fec90e69..3e2927845e 100644 --- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs +++ b/Emby.Dlna/ContentDirectory/ContentDirectory.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ContentDirectory @@ -28,7 +27,6 @@ namespace Emby.Dlna.ContentDirectory private readonly IMediaSourceManager _mediaSourceManager; private readonly IUserViewManager _userViewManager; private readonly IMediaEncoder _mediaEncoder; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; private readonly ITVSeriesManager _tvSeriesManager; public ContentDirectory(IDlnaManager dlna, @@ -38,7 +36,12 @@ namespace Emby.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) + IHttpClient httpClient, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + IUserViewManager userViewManager, + IMediaEncoder mediaEncoder, + ITVSeriesManager tvSeriesManager) : base(logger, httpClient) { _dlna = dlna; @@ -51,7 +54,6 @@ namespace Emby.Dlna.ContentDirectory _mediaSourceManager = mediaSourceManager; _userViewManager = userViewManager; _mediaEncoder = mediaEncoder; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; _tvSeriesManager = tvSeriesManager; } @@ -94,7 +96,6 @@ namespace Emby.Dlna.ContentDirectory _mediaSourceManager, _userViewManager, _mediaEncoder, - XmlReaderSettingsFactory, _tvSeriesManager) .ProcessControlRequest(request); } diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs index 84f38ff769..4f8c89e485 100644 --- a/Emby.Dlna/ContentDirectory/ControlHandler.cs +++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs @@ -25,7 +25,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.ContentDirectory @@ -51,8 +50,22 @@ namespace Emby.Dlna.ContentDirectory private readonly DeviceProfile _profile; - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) - : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler( + ILogger logger, + ILibraryManager libraryManager, + DeviceProfile profile, + string serverAddress, + string accessToken, + IImageProcessor imageProcessor, + IUserDataManager userDataManager, + User user, int systemUpdateId, + IServerConfigurationManager config, + ILocalizationManager localization, + IMediaSourceManager mediaSourceManager, + IUserViewManager userViewManager, + IMediaEncoder mediaEncoder, + ITVSeriesManager tvSeriesManager) + : base(config, logger) { _libraryManager = libraryManager; _userDataManager = userDataManager; diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 57ed0097a0..1339ba9e4c 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -20,7 +19,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; @@ -48,7 +46,7 @@ namespace Emby.Dlna.Main private readonly IDeviceDiscovery _deviceDiscovery; private SsdpDevicePublisher _Publisher; - + private readonly ISocketFactory _socketFactory; private readonly IEnvironmentInfo _environmentInfo; private readonly INetworkManager _networkManager; @@ -79,7 +77,6 @@ namespace Emby.Dlna.Main IEnvironmentInfo environmentInfo, INetworkManager networkManager, IUserViewManager userViewManager, - IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager) { _config = config; @@ -100,7 +97,8 @@ namespace Emby.Dlna.Main _networkManager = networkManager; _logger = loggerFactory.CreateLogger("Dlna"); - ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager, + ContentDirectory = new ContentDirectory.ContentDirectory( + dlnaManager, userDataManager, imageProcessor, libraryManager, @@ -112,12 +110,11 @@ namespace Emby.Dlna.Main mediaSourceManager, userViewManager, mediaEncoder, - xmlReaderSettingsFactory, tvSeriesManager); - ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory); + ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient); - MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory); + MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config); Current = this; } @@ -260,7 +257,7 @@ namespace Emby.Dlna.Main var fullService = "urn:schemas-upnp-org:device:MediaServer:1"; - _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address); + _logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString()); var descriptorUri = "/dlna/" + udn + "/description.xml"; var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri); diff --git a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs index ae8175f4a2..7381e52582 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/ControlHandler.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -36,8 +35,8 @@ namespace Emby.Dlna.MediaReceiverRegistrar }; } - public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) - : base(config, logger, xmlReaderSettingsFactory) + public ControlHandler(IServerConfigurationManager config, ILogger logger) + : base(config, logger) { } } diff --git a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs index 2b84528eab..eeae3acb6f 100644 --- a/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs +++ b/Emby.Dlna/MediaReceiverRegistrar/MediaReceiverRegistrar.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using Emby.Dlna.Service; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.MediaReceiverRegistrar @@ -10,13 +9,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar { private readonly IServerConfigurationManager _config; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config) : base(logger, httpClient) { _config = config; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public string GetServiceXml(IDictionary headers) @@ -28,7 +25,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar { return new ControlHandler( _config, - Logger, XmlReaderSettingsFactory) + Logger) .ProcessControlRequest(request); } } diff --git a/Emby.Dlna/Service/BaseControlHandler.cs b/Emby.Dlna/Service/BaseControlHandler.cs index 5f78674b88..067d5fa43f 100644 --- a/Emby.Dlna/Service/BaseControlHandler.cs +++ b/Emby.Dlna/Service/BaseControlHandler.cs @@ -7,7 +7,6 @@ using System.Xml; using Emby.Dlna.Didl; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace Emby.Dlna.Service @@ -18,13 +17,11 @@ namespace Emby.Dlna.Service protected readonly IServerConfigurationManager Config; protected readonly ILogger _logger; - protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory; - protected BaseControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + protected BaseControlHandler(IServerConfigurationManager config, ILogger logger) { Config = config; _logger = logger; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } public ControlResponse ProcessControlRequest(ControlRequest request) @@ -61,11 +58,13 @@ namespace Emby.Dlna.Service using (var streamReader = new StreamReader(request.InputXml)) { - var readerSettings = XmlReaderSettingsFactory.Create(false); - - readerSettings.CheckCharacters = false; - readerSettings.IgnoreProcessingInstructions = true; - readerSettings.IgnoreComments = true; + var readerSettings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; using (var reader = XmlReader.Create(streamReader, readerSettings)) { diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index b3bb4f740e..ec1ad6dbdf 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -44,7 +44,6 @@ using Emby.Server.Implementations.Serialization; using Emby.Server.Implementations.Session; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Emby.Server.Implementations.Xml; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -98,7 +97,6 @@ using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Updates; -using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Chapters; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Subtitles; @@ -691,8 +689,6 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); - serviceCollection.AddSingleton(new XmlReaderSettingsFactory()); - UserDataManager = new UserDataManager(LoggerFactory, ServerConfigurationManager, () => UserManager); serviceCollection.AddSingleton(UserDataManager); diff --git a/Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs b/Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs deleted file mode 100644 index 308922e6d1..0000000000 --- a/Emby.Server.Implementations/Xml/XmlReaderSettingsFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Xml; -using MediaBrowser.Model.Xml; - -namespace Emby.Server.Implementations.Xml -{ - public class XmlReaderSettingsFactory : IXmlReaderSettingsFactory - { - public XmlReaderSettings Create(bool enableValidation) - { - var settings = new XmlReaderSettings(); - - if (!enableValidation) - { - settings.ValidationType = ValidationType.None; - } - - return settings; - } - } -} diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 38458e34c8..23b9745769 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -10,7 +10,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers @@ -30,19 +29,14 @@ namespace MediaBrowser.LocalMetadata.Parsers private Dictionary _validProviderIds; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - protected IFileSystem FileSystem { get; private set; } - /// /// Initializes a new instance of the class. /// /// The logger. - public BaseItemXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) + public BaseItemXmlParser(ILogger logger, IProviderManager providerManager) { Logger = logger; ProviderManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; - FileSystem = fileSystem; } /// @@ -64,11 +58,13 @@ namespace MediaBrowser.LocalMetadata.Parsers throw new ArgumentException("The metadata file was empty or null.", nameof(metadataFile)); } - var settings = XmlReaderSettingsFactory.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; _validProviderIds = _validProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs index e595e9d068..127334625d 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs @@ -3,8 +3,6 @@ using System.Xml; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers @@ -87,7 +85,8 @@ namespace MediaBrowser.LocalMetadata.Parsers item.Item.LinkedChildren = list.ToArray(); } - public BoxSetXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) : base(logger, providerManager, xmlReaderSettingsFactory, fileSystem) + public BoxSetXmlParser(ILogger logger, IProviderManager providerManager) + : base(logger, providerManager) { } } diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index ff69cb0237..5608a0be90 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -3,8 +3,6 @@ using System.Xml; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Parsers @@ -95,7 +93,8 @@ namespace MediaBrowser.LocalMetadata.Parsers item.LinkedChildren = list.ToArray(); } - public PlaylistXmlParser(ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, IFileSystem fileSystem) : base(logger, providerManager, xmlReaderSettingsFactory, fileSystem) + public PlaylistXmlParser(ILogger logger, IProviderManager providerManager) + : base(logger, providerManager) { } } diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs index 539f818c65..2e303efab6 100644 --- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.LocalMetadata.Parsers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Providers @@ -16,19 +15,17 @@ namespace MediaBrowser.LocalMetadata.Providers { private readonly ILogger _logger; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BoxSetXmlParser(_logger, _providerManager, XmlReaderSettingsFactory, FileSystem).Fetch(result, path, cancellationToken); + new BoxSetXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs index 442a18cb98..d111ae9ba8 100644 --- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs +++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.LocalMetadata.Parsers; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Providers @@ -13,19 +12,17 @@ namespace MediaBrowser.LocalMetadata.Providers { private readonly ILogger _logger; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new PlaylistXmlParser(_logger, _providerManager, XmlReaderSettingsFactory, FileSystem).Fetch(result, path, cancellationToken); + new PlaylistXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 438b842525..30a33b729a 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -14,7 +14,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Savers @@ -23,7 +22,7 @@ namespace MediaBrowser.LocalMetadata.Savers { private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BaseXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) { FileSystem = fileSystem; ConfigurationManager = configurationManager; @@ -31,7 +30,6 @@ namespace MediaBrowser.LocalMetadata.Savers UserManager = userManager; UserDataManager = userDataManager; Logger = logger; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected IFileSystem FileSystem { get; private set; } @@ -40,9 +38,6 @@ namespace MediaBrowser.LocalMetadata.Savers protected IUserManager UserManager { get; private set; } protected IUserDataManager UserDataManager { get; private set; } protected ILogger Logger { get; private set; } - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - - protected ItemUpdateType MinimumUpdateType => ItemUpdateType.MetadataDownload; public string Name => XmlProviderUtils.Name; diff --git a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs index b4d5440a52..ea939e33b1 100644 --- a/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BoxSetXmlSaver.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Savers @@ -31,7 +30,8 @@ namespace MediaBrowser.LocalMetadata.Savers return Path.Combine(item.Path, "collection.xml"); } - public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) + public BoxSetXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } } diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs index 09bb6d8f72..35a431fa48 100644 --- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Savers @@ -49,7 +48,8 @@ namespace MediaBrowser.LocalMetadata.Savers return Path.Combine(path, "playlist.xml"); } - public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger, xmlReaderSettingsFactory) + public PlaylistXmlSaver(IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataManager, ILogger logger) + : base(fileSystem, configurationManager, libraryManager, userManager, userDataManager, logger) { } } diff --git a/MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs b/MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs deleted file mode 100644 index b39325958b..0000000000 --- a/MediaBrowser.Model/Xml/IXmlReaderSettingsFactory.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Xml; - -namespace MediaBrowser.Model.Xml -{ - public interface IXmlReaderSettingsFactory - { - XmlReaderSettings Create(bool enableValidation); - } -} diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e4bb52217c..335f2c5395 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Music @@ -28,17 +27,15 @@ namespace MediaBrowser.Providers.Music private readonly IApplicationHost _appHost; private readonly ILogger _logger; private readonly IJsonSerializer _json; - private readonly IXmlReaderSettingsFactory _xmlSettings; public static string MusicBrainzBaseUrl = "https://www.musicbrainz.org"; - public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json, IXmlReaderSettingsFactory xmlSettings) + public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json) { _httpClient = httpClient; _appHost = appHost; _logger = logger; _json = json; - _xmlSettings = xmlSettings; Current = this; } @@ -101,11 +98,13 @@ namespace MediaBrowser.Providers.Music { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; using (var reader = XmlReader.Create(oReader, settings)) { @@ -240,22 +239,20 @@ namespace MediaBrowser.Providers.Music artistId); using (var response = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = _xmlSettings.Create(false); + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; - - using (var reader = XmlReader.Create(oReader, settings)) - { - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - } + using (var reader = XmlReader.Create(oReader, settings)) + { + return ReleaseResult.Parse(reader).FirstOrDefault(); } } } @@ -267,22 +264,20 @@ namespace MediaBrowser.Providers.Music WebUtility.UrlEncode(artistName)); using (var response = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) - { - var settings = _xmlSettings.Create(false); + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; - - using (var reader = XmlReader.Create(oReader, settings)) - { - return ReleaseResult.Parse(reader).FirstOrDefault(); - } - } + using (var reader = XmlReader.Create(oReader, settings)) + { + return ReleaseResult.Parse(reader).FirstOrDefault(); } } } @@ -590,26 +585,24 @@ namespace MediaBrowser.Providers.Music var url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); using (var response = await GetMusicBrainzResponse(url, true, true, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + var result = ReleaseResult.Parse(reader).FirstOrDefault(); + + if (result != null) { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; - - using (var reader = XmlReader.Create(oReader, settings)) - { - var result = ReleaseResult.Parse(reader).FirstOrDefault(); - - if (result != null) - { - return result.ReleaseId; - } - } + return result.ReleaseId; } } } @@ -628,56 +621,54 @@ namespace MediaBrowser.Providers.Music var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId); using (var response = await GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false)) + using (var stream = response.Content) + using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - using (var stream = response.Content) + var settings = new XmlReaderSettings() { - using (var oReader = new StreamReader(stream, Encoding.UTF8)) + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var reader = XmlReader.Create(oReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; - - using (var reader = XmlReader.Create(oReader, settings)) + if (reader.NodeType == XmlNodeType.Element) { - reader.MoveToContent(); - reader.Read(); - - // Loop through each element - while (!reader.EOF && reader.ReadState == ReadState.Interactive) + switch (reader.Name) { - if (reader.NodeType == XmlNodeType.Element) - { - switch (reader.Name) + case "release-group-list": { - case "release-group-list": - { - if (reader.IsEmptyElement) - { - reader.Read(); - continue; - } - using (var subReader = reader.ReadSubtree()) - { - return GetFirstReleaseGroupId(subReader); - } - } - default: - { - reader.Skip(); - break; - } + if (reader.IsEmptyElement) + { + reader.Read(); + continue; + } + using (var subReader = reader.ReadSubtree()) + { + return GetFirstReleaseGroupId(subReader); + } + } + default: + { + reader.Skip(); + break; } - } - else - { - reader.Read(); - } } - return null; + } + else + { + reader.Read(); } } + return null; } } } diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 8d10834c32..924b712507 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -13,17 +13,14 @@ using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Xml; namespace MediaBrowser.Providers.Music { public class MusicBrainzArtistProvider : IRemoteMetadataProvider { - private readonly IXmlReaderSettingsFactory _xmlSettings; - - public MusicBrainzArtistProvider(IXmlReaderSettingsFactory xmlSettings) + public MusicBrainzArtistProvider() { - _xmlSettings = xmlSettings; + } public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken) @@ -84,11 +81,13 @@ namespace MediaBrowser.Providers.Music { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { - var settings = _xmlSettings.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; using (var reader = XmlReader.Create(oReader, settings)) { diff --git a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs index 0a2975e0f9..752c0941d0 100644 --- a/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs +++ b/MediaBrowser.Providers/TV/MissingEpisodeProvider.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Xml; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; diff --git a/MediaBrowser.Providers/TV/SeriesMetadataService.cs b/MediaBrowser.Providers/TV/SeriesMetadataService.cs index afbd838e4b..d5b0b6fd89 100644 --- a/MediaBrowser.Providers/TV/SeriesMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeriesMetadataService.cs @@ -8,7 +8,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.TV.TheTVDB; using Microsoft.Extensions.Logging; @@ -18,7 +17,6 @@ namespace MediaBrowser.Providers.TV public class SeriesMetadataService : MetadataService { private readonly ILocalizationManager _localization; - private readonly IXmlReaderSettingsFactory _xmlSettings; private readonly TvDbClientManager _tvDbClientManager; public SeriesMetadataService( @@ -29,13 +27,11 @@ namespace MediaBrowser.Providers.TV IUserDataManager userDataManager, ILibraryManager libraryManager, ILocalizationManager localization, - IXmlReaderSettingsFactory xmlSettings, TvDbClientManager tvDbClientManager ) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager) { _localization = localization; - _xmlSettings = xmlSettings; _tvDbClientManager = tvDbClientManager; } diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs new file mode 100644 index 0000000000..c8ae585740 --- /dev/null +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbPrescanTask.cs @@ -0,0 +1,396 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Net; +using MediaBrowser.Providers.TV.TheTVDB; +using Microsoft.Extensions.Logging; + +namespace MediaBrowser.Providers.TV +{ + /// + /// Class TvdbPrescanTask + /// + public class TvdbPrescanTask : ILibraryPostScanTask + { + public const string TvdbBaseUrl = "https://thetvdb.com/"; + + /// + /// The server time URL + /// + private const string ServerTimeUrl = TvdbBaseUrl + "api/Updates.php?type=none"; + + /// + /// The updates URL + /// + private const string UpdatesUrl = TvdbBaseUrl + "api/Updates.php?type=all&time={0}"; + + /// + /// The _HTTP client + /// + private readonly IHttpClient _httpClient; + /// + /// The _logger + /// + private readonly ILogger _logger; + /// + /// The _config + /// + private readonly IServerConfigurationManager _config; + private readonly IFileSystem _fileSystem; + private readonly ILibraryManager _libraryManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The HTTP client. + /// The config. + public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem, ILibraryManager libraryManager) + { + _logger = logger; + _httpClient = httpClient; + _config = config; + _fileSystem = fileSystem; + _libraryManager = libraryManager; + } + + protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + /// + /// Runs the specified progress. + /// + /// The progress. + /// The cancellation token. + /// Task. + public async Task Run(IProgress progress, CancellationToken cancellationToken) + { + var path = TvdbSeriesProvider.GetSeriesDataPath(_config.CommonApplicationPaths); + + Directory.CreateDirectory(path); + + var timestampFile = Path.Combine(path, "time.txt"); + + var timestampFileInfo = _fileSystem.GetFileInfo(timestampFile); + + // Don't check for tvdb updates anymore frequently than 24 hours + if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1) + { + return; + } + + // Find out the last time we queried tvdb for updates + var lastUpdateTime = timestampFileInfo.Exists ? File.ReadAllText(timestampFile, Encoding.UTF8) : string.Empty; + + string newUpdateTime; + + var existingDirectories = _fileSystem.GetDirectoryPaths(path) + .Select(Path.GetFileName) + .ToList(); + + var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() + { + IncludeItemTypes = new[] { typeof(Series).Name }, + Recursive = true, + GroupByPresentationUniqueKey = false, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + + }).Cast() + .ToList(); + + var seriesIdsInLibrary = seriesList + .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb))) + .Select(i => i.GetProviderId(MetadataProviders.Tvdb)) + .ToList(); + + var missingSeries = seriesIdsInLibrary.Except(existingDirectories, StringComparer.OrdinalIgnoreCase) + .ToList(); + + var enableInternetProviders = seriesList.Count == 0 ? false : seriesList[0].IsMetadataFetcherEnabled(_libraryManager.GetLibraryOptions(seriesList[0]), TvdbSeriesProvider.Current.Name); + if (!enableInternetProviders) + { + progress.Report(100); + return; + } + + // If this is our first time, update all series + if (string.IsNullOrEmpty(lastUpdateTime)) + { + // First get tvdb server time + using (var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = ServerTimeUrl, + CancellationToken = cancellationToken, + EnableHttpCompression = true, + BufferContent = false + + }, "GET").ConfigureAwait(false)) + { + // First get tvdb server time + using (var stream = response.Content) + { + newUpdateTime = GetUpdateTime(stream); + } + } + + existingDirectories.AddRange(missingSeries); + + await UpdateSeries(existingDirectories, path, null, progress, cancellationToken).ConfigureAwait(false); + } + else + { + var seriesToUpdate = await GetSeriesIdsToUpdate(existingDirectories, lastUpdateTime, cancellationToken).ConfigureAwait(false); + + newUpdateTime = seriesToUpdate.Item2; + + long.TryParse(lastUpdateTime, NumberStyles.Any, UsCulture, out var lastUpdateValue); + + var nullableUpdateValue = lastUpdateValue == 0 ? (long?)null : lastUpdateValue; + + var listToUpdate = seriesToUpdate.Item1.ToList(); + listToUpdate.AddRange(missingSeries); + + await UpdateSeries(listToUpdate, path, nullableUpdateValue, progress, cancellationToken).ConfigureAwait(false); + } + + File.WriteAllText(timestampFile, newUpdateTime, Encoding.UTF8); + progress.Report(100); + } + + /// + /// Gets the update time. + /// + /// The response. + /// System.String. + private string GetUpdateTime(Stream response) + { + // Use XmlReader for best performance + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var streamReader = new StreamReader(response, Encoding.UTF8)) + using (var reader = XmlReader.Create(streamReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Time": + { + return (reader.ReadElementContentAsString() ?? string.Empty).Trim(); + } + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + + return null; + } + + /// + /// Gets the series ids to update. + /// + /// The existing series ids. + /// The last update time. + /// The cancellation token. + /// Task{IEnumerable{System.String}}. + private async Task, string>> GetSeriesIdsToUpdate(IEnumerable existingSeriesIds, string lastUpdateTime, CancellationToken cancellationToken) + { + // First get last time + using (var response = await _httpClient.SendAsync(new HttpRequestOptions + { + Url = string.Format(UpdatesUrl, lastUpdateTime), + CancellationToken = cancellationToken, + EnableHttpCompression = true, + BufferContent = false + + }, "GET").ConfigureAwait(false)) + { + using (var stream = response.Content) + { + var data = GetUpdatedSeriesIdList(stream); + + var existingDictionary = existingSeriesIds.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); + + var seriesList = data.ids + .Where(i => !string.IsNullOrWhiteSpace(i) && existingDictionary.ContainsKey(i)); + + return new Tuple, string>(seriesList, data.updateTime); + } + } + } + + private (List ids, string updateTime) GetUpdatedSeriesIdList(Stream stream) + { + string updateTime = null; + var idList = new List(); + + // Use XmlReader for best performance + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + + using (var streamReader = new StreamReader(stream, Encoding.UTF8)) + using (var reader = XmlReader.Create(streamReader, settings)) + { + reader.MoveToContent(); + reader.Read(); + + // Loop through each element + while (!reader.EOF && reader.ReadState == ReadState.Interactive) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Time": + { + updateTime = (reader.ReadElementContentAsString() ?? string.Empty).Trim(); + break; + } + case "Series": + { + var id = (reader.ReadElementContentAsString() ?? string.Empty).Trim(); + idList.Add(id); + break; + } + default: + reader.Skip(); + break; + } + } + else + { + reader.Read(); + } + } + } + + return (idList, updateTime); + } + + /// + /// Updates the series. + /// + /// The series ids. + /// The series data path. + /// The last tv db update time. + /// The progress. + /// The cancellation token. + /// Task. + private async Task UpdateSeries(List seriesIds, string seriesDataPath, long? lastTvDbUpdateTime, IProgress progress, CancellationToken cancellationToken) + { + var numComplete = 0; + + var seriesList = _libraryManager.GetItemList(new InternalItemsQuery() + { + IncludeItemTypes = new[] { typeof(Series).Name }, + Recursive = true, + GroupByPresentationUniqueKey = false, + DtoOptions = new DtoOptions(false) + { + EnableImages = false + } + + }).Cast(); + + // Gather all series into a lookup by tvdb id + var allSeries = seriesList + .Where(i => !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb))) + .ToLookup(i => i.GetProviderId(MetadataProviders.Tvdb)); + + foreach (var seriesId in seriesIds) + { + // Find the preferred language(s) for the movie in the library + var languages = allSeries[seriesId] + .Select(i => i.GetPreferredMetadataLanguage()) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToList(); + + foreach (var language in languages) + { + try + { + await UpdateSeries(seriesId, seriesDataPath, lastTvDbUpdateTime, language, cancellationToken).ConfigureAwait(false); + } + catch (HttpException ex) + { + _logger.LogError(ex, "Error updating tvdb series id {ID}, language {Language}", seriesId, language); + + // Already logged at lower levels, but don't fail the whole operation, unless timed out + // We have to fail this to make it run again otherwise new episode data could potentially be missing + if (ex.IsTimedOut) + { + throw; + } + } + } + + numComplete++; + double percent = numComplete; + percent /= seriesIds.Count; + percent *= 100; + + progress.Report(percent); + } + } + + /// + /// Updates the series. + /// + /// The id. + /// The series data path. + /// The last tv db update time. + /// The preferred metadata language. + /// The cancellation token. + /// Task. + private Task UpdateSeries(string id, string seriesDataPath, long? lastTvDbUpdateTime, string preferredMetadataLanguage, CancellationToken cancellationToken) + { + _logger.LogInformation("Updating series from tvdb " + id + ", language " + preferredMetadataLanguage); + + seriesDataPath = Path.Combine(seriesDataPath, id); + + Directory.CreateDirectory(seriesDataPath); + + return TvdbSeriesProvider.Current.DownloadSeriesZip(id, MetadataProviders.Tvdb.ToString(), null, null, seriesDataPath, lastTvDbUpdateTime, preferredMetadataLanguage, cancellationToken); + } + } +} diff --git a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs index 9c24e4c987..09b056c0cb 100644 --- a/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs +++ b/MediaBrowser.Providers/TV/TheTVDB/TvdbSeriesProvider.cs @@ -23,7 +23,6 @@ namespace MediaBrowser.Providers.TV.TheTVDB { internal static TvdbSeriesProvider Current { get; private set; } private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localizationManager; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f20dbbb6ea..aa5313eb72 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -13,8 +13,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Configuration; using MediaBrowser.XbmcMetadata.Savers; using Microsoft.Extensions.Logging; @@ -28,9 +26,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The logger /// protected ILogger Logger { get; private set; } - protected IFileSystem FileSystem { get; private set; } protected IProviderManager ProviderManager { get; private set; } - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IConfigurationManager _config; @@ -39,13 +35,11 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// Initializes a new instance of the class. /// - public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) { Logger = logger; _config = config; ProviderManager = providerManager; - FileSystem = fileSystem; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } /// @@ -68,11 +62,13 @@ namespace MediaBrowser.XbmcMetadata.Parsers throw new ArgumentException("The metadata file was empty or null.", nameof(metadataFile)); } - var settings = XmlReaderSettingsFactory.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; _validProviderIds = _validProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -935,19 +931,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers value = value.Trim().Trim(separator); - return string.IsNullOrWhiteSpace(value) ? Array.Empty() : Split(value, separator, StringSplitOptions.RemoveEmptyEntries); - } - - /// - /// Provides an additional overload for string.split - /// - /// The val. - /// The separators. - /// The options. - /// System.String[][]. - private string[] Split(string val, char[] separators, StringSplitOptions options) - { - return val.Split(separators, options); + return string.IsNullOrWhiteSpace(value) ? Array.Empty() : value.Split(separator, StringSplitOptions.RemoveEmptyEntries); } } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index a8cb6ac40d..7f68ba256e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -8,8 +8,6 @@ using System.Xml; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -220,7 +218,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index a3e48d30d9..526cc62b0e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -7,8 +7,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -124,14 +122,16 @@ namespace MediaBrowser.XbmcMetadata.Parsers using (var stringReader = new StringReader("" + xml + "")) { // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions + var settings = new XmlReaderSettings() + { + ValidationType = ValidationType.None, + CheckCharacters = false, + IgnoreProcessingInstructions = true, + IgnoreComments = true + }; + try { - var settings = XmlReaderSettingsFactory.Create(false); - - settings.CheckCharacters = false; - settings.IgnoreProcessingInstructions = true; - settings.IgnoreComments = true; - // Use XmlReader for best performance using (var reader = XmlReader.Create(stringReader, settings)) { @@ -167,7 +167,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs index 17f36d82d7..882f3a9d3a 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs @@ -3,8 +3,6 @@ using System.Xml; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -42,7 +40,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public SeasonNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public SeasonNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 700656b652..b0f25ae64e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -5,8 +5,6 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Parsers @@ -94,7 +92,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - public SeriesNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager, IFileSystem fileSystem, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(logger, config, providerManager, fileSystem, xmlReaderSettingsFactory) + public SeriesNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(logger, config, providerManager) { } } diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index 1e22bf3582..6e6a22794d 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging; @@ -15,20 +14,18 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BaseNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(result, path, cancellationToken); + new BaseNfoParser(_logger, _config, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index ad811b4754..20abfc7f3a 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging; @@ -15,20 +14,18 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - new BaseNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(result, path, cancellationToken); + new BaseNfoParser(_logger, _config, _providerManager).Fetch(result, path, cancellationToken); } protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService) diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index 94f104f61b..28a0514d57 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -4,7 +4,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using MediaBrowser.XbmcMetadata.Savers; using Microsoft.Extensions.Logging; @@ -17,15 +16,13 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) @@ -34,7 +31,7 @@ namespace MediaBrowser.XbmcMetadata.Providers { Item = result.Item }; - new MovieNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(tmpItem, path, cancellationToken); + new MovieNfoParser(_logger, _config, _providerManager).Fetch(tmpItem, path, cancellationToken); result.Item = (T)tmpItem.Item; result.People = tmpItem.People; diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs index bf05f0f38b..f90f283cf3 100644 --- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs @@ -5,7 +5,6 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using MediaBrowser.XbmcMetadata.Parsers; using Microsoft.Extensions.Logging; @@ -16,22 +15,20 @@ namespace MediaBrowser.XbmcMetadata.Providers private readonly ILogger _logger; private readonly IConfigurationManager _config; private readonly IProviderManager _providerManager; - protected IXmlReaderSettingsFactory XmlReaderSettingsFactory { get; private set; } - public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) + public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem) { _logger = logger; _config = config; _providerManager = providerManager; - XmlReaderSettingsFactory = xmlReaderSettingsFactory; } protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { var images = new List(); - new EpisodeNfoParser(_logger, _config, _providerManager, FileSystem, XmlReaderSettingsFactory).Fetch(result, images, path, cancellationToken); + new EpisodeNfoParser(_logger, _config, _providerManager).Fetch(result, images, path, cancellationToken); result.Images = images; } diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs index 77b3b3781b..d21164c022 100644 --- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs @@ -3,28 +3,30 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; namespace MediaBrowser.XbmcMetadata.Providers { public class MovieNfoProvider : BaseVideoNfoProvider { - public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, logger, config, providerManager, xmlReaderSettingsFactory) + public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(fileSystem, logger, config, providerManager) { } } public class MusicVideoNfoProvider : BaseVideoNfoProvider { - public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(fileSystem, logger, config, providerManager, xmlReaderSettingsFactory) + public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) + : base(fileSystem, logger, config, providerManager) { } } public class VideoNfoProvider : BaseVideoNfoProvider public class TypeMapper { - private readonly IAssemblyInfo _assemblyInfo; - /// /// This holds all the types in the running assemblies so that we can de-serialize properly when we don't have strong types /// private readonly ConcurrentDictionary _typeMap = new ConcurrentDictionary(); - public TypeMapper(IAssemblyInfo assemblyInfo) + public TypeMapper() { - _assemblyInfo = assemblyInfo; } /// @@ -45,8 +41,7 @@ namespace Emby.Server.Implementations.Data /// Type. private Type LookupType(string typeName) { - return _assemblyInfo - .GetCurrentAssemblies() + return AppDomain.CurrentDomain.GetAssemblies() .Select(a => a.GetType(typeName)) .FirstOrDefault(t => t != null); } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index fceb82ba19..f4eddb08ba 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -33,7 +33,6 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -58,7 +57,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV private readonly IProviderManager _providerManager; private readonly IMediaEncoder _mediaEncoder; private readonly IProcessFactory _processFactory; - private readonly IAssemblyInfo _assemblyInfo; private IMediaSourceManager _mediaSourceManager; public static EmbyTV Current; @@ -74,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public EmbyTV(IServerApplicationHost appHost, IStreamHelper streamHelper, IMediaSourceManager mediaSourceManager, - IAssemblyInfo assemblyInfo, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, @@ -101,7 +98,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _processFactory = processFactory; _liveTvManager = (LiveTvManager)liveTvManager; _jsonSerializer = jsonSerializer; - _assemblyInfo = assemblyInfo; _mediaSourceManager = mediaSourceManager; _streamHelper = streamHelper; diff --git a/Emby.Server.Implementations/Reflection/AssemblyInfo.cs b/Emby.Server.Implementations/Reflection/AssemblyInfo.cs deleted file mode 100644 index 9d16fe43f6..0000000000 --- a/Emby.Server.Implementations/Reflection/AssemblyInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.IO; -using System.Reflection; -using MediaBrowser.Model.Reflection; - -namespace Emby.Server.Implementations.Reflection -{ - public class AssemblyInfo : IAssemblyInfo - { - public Stream GetManifestResourceStream(Type type, string resource) - { - return type.Assembly.GetManifestResourceStream(resource); - } - - public string[] GetManifestResourceNames(Type type) - { - return type.Assembly.GetManifestResourceNames(); - } - - public Assembly[] GetCurrentAssemblies() - { - return AppDomain.CurrentDomain.GetAssemblies(); - } - } -} diff --git a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs b/MediaBrowser.Model/Reflection/IAssemblyInfo.cs deleted file mode 100644 index 5c4536c1c1..0000000000 --- a/MediaBrowser.Model/Reflection/IAssemblyInfo.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using System.IO; -using System.Reflection; - -namespace MediaBrowser.Model.Reflection -{ - public interface IAssemblyInfo - { - Stream GetManifestResourceStream(Type type, string resource); - string[] GetManifestResourceNames(Type type); - - Assembly[] GetCurrentAssemblies(); - } -} diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 531978e1dd..aa0208495b 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -13,7 +13,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.Plugins; -using MediaBrowser.Model.Reflection; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.Extensions.Logging; @@ -117,20 +116,26 @@ namespace MediaBrowser.WebDashboard.Api private readonly IFileSystem _fileSystem; private readonly ILocalizationManager _localization; private readonly IJsonSerializer _jsonSerializer; - private readonly IAssemblyInfo _assemblyInfo; private IResourceFileManager _resourceFileManager; /// /// Initializes a new instance of the class. /// - public DashboardService(IServerApplicationHost appHost, IResourceFileManager resourceFileManager, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem, ILocalizationManager localization, IJsonSerializer jsonSerializer, IAssemblyInfo assemblyInfo, ILogger logger, IHttpResultFactory resultFactory) + public DashboardService( + IServerApplicationHost appHost, + IResourceFileManager resourceFileManager, + IServerConfigurationManager serverConfigurationManager, + IFileSystem fileSystem, + ILocalizationManager localization, + IJsonSerializer jsonSerializer, + ILogger logger, + IHttpResultFactory resultFactory) { _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; _fileSystem = fileSystem; _localization = localization; _jsonSerializer = jsonSerializer; - _assemblyInfo = assemblyInfo; _logger = logger; _resultFactory = resultFactory; _resourceFileManager = resourceFileManager; @@ -187,7 +192,7 @@ namespace MediaBrowser.WebDashboard.Api if (altPage != null) { plugin = altPage.Item2; - stream = _assemblyInfo.GetManifestResourceStream(plugin.GetType(), altPage.Item1.EmbeddedResourcePath); + stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath); isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase); isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html"); From f486f5966f2fb9a3cf266ee816b8c247f0de5482 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 09:56:03 -0800 Subject: [PATCH 115/166] Update Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs Co-Authored-By: LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> --- .../Library/DefaultAuthenticationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 526509f439..3ec1f81d31 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.Library } else { - throw new Exception(String.Format($"Requested crypto method not available in provider: {readyHash.Id}")); + 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); From 8c609bc9ce9f548161c3d950706653a525f7e1de Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 19:04:09 +0100 Subject: [PATCH 116/166] Reduce aspnet imports --- Emby.Dlna/ControlRequest.cs | 1 - Emby.Dlna/Emby.Dlna.csproj | 5 +++ Emby.Dlna/PlayTo/PlayToController.cs | 1 - .../MediaBrowser.Common.csproj | 3 +- .../Net/WebSocketConnectEventArgs.cs | 41 ------------------- MediaBrowser.Model/MediaBrowser.Model.csproj | 5 +-- 6 files changed, 9 insertions(+), 47 deletions(-) delete mode 100644 MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs index 907d437f85..8c227159c4 100644 --- a/Emby.Dlna/ControlRequest.cs +++ b/Emby.Dlna/ControlRequest.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Http; diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj index 71ded23373..4c07087c53 100644 --- a/Emby.Dlna/Emby.Dlna.csproj +++ b/Emby.Dlna/Emby.Dlna.csproj @@ -58,4 +58,9 @@ + + + + + diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 8d7d59249a..67d5cfef42 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -21,7 +21,6 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Session; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Primitives; namespace Emby.Dlna.PlayTo { diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 715f4fccd3..05b48a2a12 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,6 +13,7 @@ + diff --git a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs b/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs deleted file mode 100644 index 107e674216..0000000000 --- a/MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using MediaBrowser.Model.Services; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; - -namespace MediaBrowser.Controller.Net -{ - /// - /// Class WebSocketConnectEventArgs - /// - public class WebSocketConnectingEventArgs : EventArgs - { - /// - /// Gets or sets the URL. - /// - /// The URL. - public string Url { get; set; } - /// - /// Gets or sets the endpoint. - /// - /// The endpoint. - public string Endpoint { get; set; } - /// - /// Gets or sets the query string. - /// - /// The query string. - public IQueryCollection QueryString { get; set; } - /// - /// Gets or sets a value indicating whether [allow connection]. - /// - /// true if [allow connection]; otherwise, false. - public bool AllowConnection { get; set; } - - public WebSocketConnectingEventArgs() - { - QueryString = new QueryCollection(); - AllowConnection = true; - } - } - -} diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 2f1ea13d9b..3de2cca2d2 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -1,4 +1,4 @@ - + Jellyfin Contributors @@ -13,8 +13,7 @@ - - + From e4c5d51860b5e421abac8d453c4655188074501d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 19:04:50 +0100 Subject: [PATCH 117/166] Update MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs Co-Authored-By: cvium --- .../Playback/Progressive/BaseProgressiveStreamingService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 399ac2be09..f43f26b2f3 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback.Progressive // Headers only if (isHeadRequest) { - return ResultFactory.GetResult(null, new byte[] { }, contentType, responseHeaders); + return ResultFactory.GetResult(null, Array.Empty(), contentType, responseHeaders); } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); From dfff68b2f4ca8d3da59508eb4cbe2e751ed76f49 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 19:05:47 +0100 Subject: [PATCH 118/166] Make SkipLogExtensions static --- Emby.Server.Implementations/HttpServer/FileWriter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/HttpServer/FileWriter.cs b/Emby.Server.Implementations/HttpServer/FileWriter.cs index 0e989994b1..c4d2a70e23 100644 --- a/Emby.Server.Implementations/HttpServer/FileWriter.cs +++ b/Emby.Server.Implementations/HttpServer/FileWriter.cs @@ -148,7 +148,7 @@ namespace Emby.Server.Implementations.HttpServer } } - private readonly string[] SkipLogExtensions = { + private static readonly string[] SkipLogExtensions = { ".js", ".html", ".css" From 65c0b486aaa6d02855a6915edfb880da691b4709 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 19:27:43 +0100 Subject: [PATCH 119/166] Remove dead code What it says on the tin --- DvdLib/Ifo/AudioAttributes.cs | 36 ---------------- DvdLib/Ifo/PgcCommandTable.cs | 17 -------- DvdLib/Ifo/ProgramChain.cs | 4 -- DvdLib/Ifo/VideoAttributes.cs | 46 -------------------- Emby.Dlna/PlayTo/CurrentIdEventArgs.cs | 9 ---- Emby.Dlna/PlayTo/PlaylistItemFactory.cs | 2 - Emby.Dlna/PlayTo/TransportStateEventArgs.cs | 9 ---- Emby.Dlna/PlayTo/uParser.cs | 47 --------------------- Emby.Dlna/PlayTo/uParserObject.cs | 9 ---- 9 files changed, 179 deletions(-) delete mode 100644 DvdLib/Ifo/AudioAttributes.cs delete mode 100644 DvdLib/Ifo/PgcCommandTable.cs delete mode 100644 DvdLib/Ifo/VideoAttributes.cs delete mode 100644 Emby.Dlna/PlayTo/CurrentIdEventArgs.cs delete mode 100644 Emby.Dlna/PlayTo/TransportStateEventArgs.cs delete mode 100644 Emby.Dlna/PlayTo/uParser.cs delete mode 100644 Emby.Dlna/PlayTo/uParserObject.cs diff --git a/DvdLib/Ifo/AudioAttributes.cs b/DvdLib/Ifo/AudioAttributes.cs deleted file mode 100644 index b76f9fc05e..0000000000 --- a/DvdLib/Ifo/AudioAttributes.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace DvdLib.Ifo -{ - public enum AudioCodec - { - AC3 = 0, - MPEG1 = 2, - MPEG2ext = 3, - LPCM = 4, - DTS = 6, - } - - public enum ApplicationMode - { - Unspecified = 0, - Karaoke = 1, - Surround = 2, - } - - public class AudioAttributes - { - public readonly AudioCodec Codec; - public readonly bool MultichannelExtensionPresent; - public readonly ApplicationMode Mode; - public readonly byte QuantDRC; - public readonly byte SampleRate; - public readonly byte Channels; - public readonly ushort LanguageCode; - public readonly byte LanguageExtension; - public readonly byte CodeExtension; - } - - public class MultiChannelExtension - { - - } -} diff --git a/DvdLib/Ifo/PgcCommandTable.cs b/DvdLib/Ifo/PgcCommandTable.cs deleted file mode 100644 index d329fcba2a..0000000000 --- a/DvdLib/Ifo/PgcCommandTable.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; - -namespace DvdLib.Ifo -{ - public class ProgramChainCommandTable - { - public readonly ushort LastByteAddress; - public readonly List PreCommands; - public readonly List PostCommands; - public readonly List CellCommands; - } - - public class VirtualMachineCommand - { - public readonly byte[] Command; - } -} diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs index 80889738f1..7b003005b9 100644 --- a/DvdLib/Ifo/ProgramChain.cs +++ b/DvdLib/Ifo/ProgramChain.cs @@ -25,13 +25,10 @@ namespace DvdLib.Ifo public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries private ushort _nextProgramNumber; - public readonly ProgramChain Next; private ushort _prevProgramNumber; - public readonly ProgramChain Previous; private ushort _goupProgramNumber; - public readonly ProgramChain Goup; // ?? maybe Group public ProgramPlaybackMode PlaybackMode { get; private set; } public uint ProgramCount { get; private set; } @@ -40,7 +37,6 @@ namespace DvdLib.Ifo public byte[] Palette { get; private set; } // 16*4 entries private ushort _commandTableOffset; - public readonly ProgramChainCommandTable CommandTable; private ushort _programMapOffset; private ushort _cellPlaybackOffset; diff --git a/DvdLib/Ifo/VideoAttributes.cs b/DvdLib/Ifo/VideoAttributes.cs deleted file mode 100644 index 8b3996715c..0000000000 --- a/DvdLib/Ifo/VideoAttributes.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace DvdLib.Ifo -{ - public enum VideoCodec - { - MPEG1 = 0, - MPEG2 = 1, - } - - public enum VideoFormat - { - NTSC = 0, - PAL = 1, - } - - public enum AspectRatio - { - ar4to3 = 0, - ar16to9 = 3 - } - - public enum FilmMode - { - None = -1, - Camera = 0, - Film = 1, - } - - public class VideoAttributes - { - public readonly VideoCodec Codec; - public readonly VideoFormat Format; - public readonly AspectRatio Aspect; - public readonly bool AutomaticPanScan; - public readonly bool AutomaticLetterBox; - public readonly bool Line21CCField1; - public readonly bool Line21CCField2; - public readonly int Width; - public readonly int Height; - public readonly bool Letterboxed; - public readonly FilmMode FilmMode; - - public VideoAttributes() - { - } - } -} diff --git a/Emby.Dlna/PlayTo/CurrentIdEventArgs.cs b/Emby.Dlna/PlayTo/CurrentIdEventArgs.cs deleted file mode 100644 index fdf435bcf3..0000000000 --- a/Emby.Dlna/PlayTo/CurrentIdEventArgs.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Emby.Dlna.PlayTo -{ - public class CurrentIdEventArgs : EventArgs - { - public string Id { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs index aceb634e33..446d8e1e6e 100644 --- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs @@ -9,8 +9,6 @@ namespace Emby.Dlna.PlayTo { public class PlaylistItemFactory { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public PlaylistItem Create(Photo item, DeviceProfile profile) { var playlistItem = new PlaylistItem diff --git a/Emby.Dlna/PlayTo/TransportStateEventArgs.cs b/Emby.Dlna/PlayTo/TransportStateEventArgs.cs deleted file mode 100644 index 7dcd39e107..0000000000 --- a/Emby.Dlna/PlayTo/TransportStateEventArgs.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Emby.Dlna.PlayTo -{ - public class TransportStateEventArgs : EventArgs - { - public TRANSPORTSTATE State { get; set; } - } -} diff --git a/Emby.Dlna/PlayTo/uParser.cs b/Emby.Dlna/PlayTo/uParser.cs deleted file mode 100644 index 3a0ffffd41..0000000000 --- a/Emby.Dlna/PlayTo/uParser.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; - -namespace Emby.Dlna.PlayTo -{ - public class uParser - { - public static IList ParseBrowseXml(XDocument doc) - { - if (doc == null) - { - throw new ArgumentException("doc"); - } - - var list = new List(); - - var document = doc.Document; - - if (document == null) - return list; - - var item = (from result in document.Descendants("Result") select result).FirstOrDefault(); - - if (item == null) - return list; - - var uPnpResponse = XElement.Parse((string)item); - - var uObjects = from container in uPnpResponse.Elements(uPnpNamespaces.containers) - select new uParserObject { Element = container }; - - var uObjects2 = from container in uPnpResponse.Elements(uPnpNamespaces.items) - select new uParserObject { Element = container }; - - list.AddRange(uObjects.Concat(uObjects2).Select(CreateObjectFromXML).Where(uObject => uObject != null)); - - return list; - } - - public static uBaseObject CreateObjectFromXML(uParserObject uItem) - { - return UpnpContainer.Create(uItem.Element); - } - } -} diff --git a/Emby.Dlna/PlayTo/uParserObject.cs b/Emby.Dlna/PlayTo/uParserObject.cs deleted file mode 100644 index 87a7f69c62..0000000000 --- a/Emby.Dlna/PlayTo/uParserObject.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Xml.Linq; - -namespace Emby.Dlna.PlayTo -{ - public class uParserObject - { - public XElement Element { get; set; } - } -} From c5fce647defd2eace2d2431ccc52ffe1d027c184 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 15:54:30 +0100 Subject: [PATCH 120/166] Cleanup/simplification * Removed useless copies/allocations * Reduced unneeded complexity --- .../Collections/CollectionImageProvider.cs | 4 +- .../IO/ManagedFileSystem.cs | 7 +- .../Images/BaseDynamicImageProvider.cs | 58 +++++------- .../Playlists/PlaylistImageProvider.cs | 13 +-- .../SocketSharp/RequestMono.cs | 17 ++-- .../SocketSharp/WebSocketSharpRequest.cs | 4 +- .../CollectionFolderImageProvider.cs | 9 +- .../UserViews/DynamicImageProvider.cs | 13 +-- .../UserViews/FolderImageProvider.cs | 4 +- MediaBrowser.Api/Images/ImageService.cs | 91 ++++++++++--------- MediaBrowser.Controller/Entities/BaseItem.cs | 26 ++++-- MediaBrowser.Controller/Entities/Folder.cs | 2 +- .../Entities/ItemImageInfo.cs | 16 +--- .../Entities/Movies/BoxSet.cs | 2 +- .../Entities/Movies/Movie.cs | 8 +- .../Entities/TV/Episode.cs | 2 +- MediaBrowser.Controller/Entities/TV/Series.cs | 3 +- MediaBrowser.Controller/Entities/Video.cs | 2 +- MediaBrowser.Model/IO/IFileSystem.cs | 3 +- 19 files changed, 127 insertions(+), 157 deletions(-) diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs index cdfb5cadf1..0244c4a684 100644 --- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs +++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs @@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Collections return base.Supports(item); } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { var playlist = (BoxSet)item; @@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Collections .ToList(); } - protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index a64dfb607b..421592fada 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -7,7 +7,6 @@ using System.Text; using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.IO @@ -711,20 +710,20 @@ namespace Emby.Server.Implementations.IO return GetFiles(path, null, false, recursive); } - public virtual IEnumerable GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) + public virtual IEnumerable GetFiles(string path, IReadOnlyList 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 => { diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 109c21f18d..46f209b4b4 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 : IHasItemChangeMonitor, IForcedProvider, ICustomMetadataProvider, IHasOrder where T : BaseItem { + protected virtual IReadOnlyCollection 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 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 FetchToFileInternal(BaseItem item, - List itemsWithImages, + IReadOnlyList itemsWithImages, ImageType imageType, CancellationToken cancellationToken) { @@ -119,9 +110,9 @@ namespace Emby.Server.Implementations.Images return ItemUpdateType.ImageUpdate; } - protected abstract List GetItemsWithImages(BaseItem item); + protected abstract IReadOnlyList GetItemsWithImages(BaseItem item); - protected string CreateThumbCollage(BaseItem primaryItem, List items, string outputPath) + protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable 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); - if (image != null && image.IsLocalFile) { return image.Path; } + return null; }) .Where(i => !string.IsNullOrEmpty(i)); } - protected string CreatePosterCollage(BaseItem primaryItem, List items, string outputPath) + protected string CreatePosterCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 400, 600); } - protected string CreateSquareCollage(BaseItem primaryItem, List items, string outputPath) + protected string CreateSquareCollage(BaseItem primaryItem, IEnumerable items, string outputPath) { return CreateCollage(primaryItem, items, outputPath, 600, 600); } - protected string CreateThumbCollage(BaseItem primaryItem, List items, string outputPath, int width, int height) + protected string CreateThumbCollage(BaseItem primaryItem, IEnumerable items, string outputPath, int width, int height) { return CreateCollage(primaryItem, items, outputPath, width, height); } - private string CreateCollage(BaseItem primaryItem, List items, string outputPath, int width, int height) + private string CreateCollage(BaseItem primaryItem, IEnumerable 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 itemsWithImages, + IReadOnlyCollection 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 itemsWithImages, string outputPathWithoutExtension, ImageType imageType) + protected string CreateSingleImage(IEnumerable 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/Playlists/PlaylistImageProvider.cs b/Emby.Server.Implementations/Playlists/PlaylistImageProvider.cs index 8a7c1492d4..cad66a80fd 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 GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { var playlist = (Playlist)item; @@ -78,7 +78,7 @@ namespace Emby.Server.Implementations.Playlists _libraryManager = libraryManager; } - protected override List GetItemsWithImages(BaseItem item) + protected override IReadOnlyList 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 GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { return _libraryManager.GetItemList(new InternalItemsQuery { @@ -116,11 +115,5 @@ namespace Emby.Server.Implementations.Playlists DtoOptions = new DtoOptions(false) }); } - - //protected override Task CreateImage(IHasMetadata item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) - //{ - // return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); - //} } - } diff --git a/Emby.Server.Implementations/SocketSharp/RequestMono.cs b/Emby.Server.Implementations/SocketSharp/RequestMono.cs index 5e29e40584..373f6d7580 100644 --- a/Emby.Server.Implementations/SocketSharp/RequestMono.cs +++ b/Emby.Server.Implementations/SocketSharp/RequestMono.cs @@ -79,7 +79,7 @@ namespace Emby.Server.Implementations.SocketSharp byte[] copy = new byte[e.Length]; input.Position = e.Start; - input.Read(copy, 0, (int)e.Length); + await input.ReadAsync(copy, 0, (int)e.Length).ConfigureAwait(false); form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length)); } @@ -98,11 +98,11 @@ namespace Emby.Server.Implementations.SocketSharp var form = new WebROCollection(); files = new Dictionary(); - if (IsContentType("multipart/form-data", true)) + if (IsContentType("multipart/form-data")) { await LoadMultiPart(form).ConfigureAwait(false); } - else if (IsContentType("application/x-www-form-urlencoded", true)) + else if (IsContentType("application/x-www-form-urlencoded")) { await LoadWwwForm(form).ConfigureAwait(false); } @@ -200,19 +200,14 @@ namespace Emby.Server.Implementations.SocketSharp return false; } - private bool IsContentType(string ct, bool starts_with) + private bool IsContentType(string ct) { - if (ct == null || ContentType == null) + if (ContentType == null) { return false; } - if (starts_with) - { - return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); - } - - return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase); + return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase); } private async Task LoadWwwForm(WebROCollection form) diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bc002dc4ce..10b7c45149 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IO; using System.Net; using System.Text; -using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; @@ -405,8 +404,7 @@ namespace Emby.Server.Implementations.SocketSharp return null; } - var path = sbPathInfo.ToString(); - return path.Length > 1 ? path.TrimEnd('/') : "/"; + return sbPathInfo.Length > 1 ? sbPathInfo.ToString().TrimEnd('/') : "/"; } public string UserAgent => request.Headers[HeaderNames.UserAgent]; diff --git a/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/UserViews/CollectionFolderImageProvider.cs index ce6c2cd87d..a3f3f6cb4d 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 GetItemsWithImages(BaseItem item) + protected override IReadOnlyList 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 itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection 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 4ec68e550d..f485204436 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 GetItemsWithImages(BaseItem item) + protected override IReadOnlyList 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 itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection 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 c810004ab2..4655cd928a 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 GetItemsWithImages(BaseItem item) + protected override IReadOnlyList GetItemsWithImages(BaseItem item) { return _libraryManager.GetItemList(new InternalItemsQuery { @@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.UserViews }); } - protected override string CreateImage(BaseItem item, List itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) + protected override string CreateImage(BaseItem item, IReadOnlyCollection itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex) { return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary); } diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index c25204bf2d..10bbc9e5d8 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -312,35 +312,35 @@ namespace MediaBrowser.Api.Images private ImageInfo GetImageInfo(BaseItem item, ItemImageInfo info, int? imageIndex) { + int? width = null; + int? height = null; + long length = 0; + try { - int? width = null; - int? height = null; - long length = 0; - - try + if (info.IsLocalFile) { - if (info.IsLocalFile) + var fileInfo = _fileSystem.GetFileInfo(info.Path); + length = fileInfo.Length; + + ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); + width = size.Width; + height = size.Height; + + if (width <= 0 || height <= 0) { - var fileInfo = _fileSystem.GetFileInfo(info.Path); - length = fileInfo.Length; - - ImageDimensions size = _imageProcessor.GetImageDimensions(item, info, true); - width = size.Width; - height = size.Height; - - if (width <= 0 || height <= 0) - { - width = null; - height = null; - } - + width = null; + height = null; } } - catch - { + } + catch (Exception ex) + { + Logger.LogError(ex, "Error getting image information for {Item}", item.Name); + } - } + try + { return new ImageInfo { Path = info.Path, @@ -354,7 +354,7 @@ namespace MediaBrowser.Api.Images } catch (Exception ex) { - Logger.LogError(ex, "Error getting image information for {path}", info.Path); + Logger.LogError(ex, "Error getting image information for {Path}", info.Path); return null; } @@ -519,16 +519,16 @@ namespace MediaBrowser.Api.Images request.AddPlayedIndicator = true; } } + if (request.PercentPlayed.HasValue) { request.UnplayedCount = null; } - if (request.UnplayedCount.HasValue) + + if (request.UnplayedCount.HasValue + && request.UnplayedCount.Value <= 0) { - if (request.UnplayedCount.Value <= 0) - { - request.UnplayedCount = null; - } + request.UnplayedCount = null; } if (item == null) @@ -542,7 +542,6 @@ namespace MediaBrowser.Api.Images } var imageInfo = GetImageInfo(request, item); - if (imageInfo == null) { var displayText = item == null ? itemId.ToString() : item.Name; @@ -550,7 +549,6 @@ namespace MediaBrowser.Api.Images } IImageEnhancer[] supportedImageEnhancers; - if (_imageProcessor.ImageEnhancers.Length > 0) { if (item == null) @@ -565,13 +563,15 @@ namespace MediaBrowser.Api.Images supportedImageEnhancers = Array.Empty(); } - var cropwhitespace = request.Type == ImageType.Logo || - request.Type == ImageType.Art; - + bool cropwhitespace; if (request.CropWhitespace.HasValue) { cropwhitespace = request.CropWhitespace.Value; } + else + { + cropwhitespace = request.Type == ImageType.Logo || request.Type == ImageType.Art; + } var outputFormats = GetOutputFormats(request); @@ -653,12 +653,10 @@ namespace MediaBrowser.Api.Images private ImageFormat[] GetOutputFormats(ImageRequest request) { - if (!string.IsNullOrWhiteSpace(request.Format)) + if (!string.IsNullOrWhiteSpace(request.Format) + && Enum.TryParse(request.Format, true, out ImageFormat format)) { - if (Enum.TryParse(request.Format, true, out ImageFormat format)) - { - return new ImageFormat[] { format }; - } + return new ImageFormat[] { format }; } return GetClientSupportedFormats(); @@ -666,8 +664,19 @@ namespace MediaBrowser.Api.Images private ImageFormat[] GetClientSupportedFormats() { - //logger.LogDebug("Request types: {0}", string.Join(",", Request.AcceptTypes ?? Array.Empty())); - var supportedFormats = (Request.AcceptTypes ?? Array.Empty()).Select(i => i.Split(';')[0]).ToArray(); + var supportedFormats = Request.AcceptTypes ?? Array.Empty(); + if (supportedFormats.Length > 0) + { + for (int i = 0; i < supportedFormats.Length; i++) + { + int index = supportedFormats[i].IndexOf(';'); + if (index != -1) + { + supportedFormats[i] = supportedFormats[i].Substring(0, index); + } + } + } + var acceptParam = Request.QueryString["accept"]; var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); @@ -700,7 +709,7 @@ namespace MediaBrowser.Api.Images return formats.ToArray(); } - private bool SupportsFormat(string[] requestAcceptTypes, string acceptParam, string format, bool acceptAll) + private bool SupportsFormat(IEnumerable requestAcceptTypes, string acceptParam, string format, bool acceptAll) { var mimeType = "image/" + format; diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 43fee79a10..39b6eedfeb 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -36,10 +36,20 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BaseItem : IHasProviderIds, IHasLookupInfo { - protected static MetadataFields[] EmptyMetadataFieldsArray = Array.Empty(); - protected static MediaUrl[] EmptyMediaUrlArray = Array.Empty(); - protected static ItemImageInfo[] EmptyItemImageInfoArray = Array.Empty(); - public static readonly LinkedChild[] EmptyLinkedChildArray = Array.Empty(); + private static readonly List _supportedExtensions = new List(SupportedImageExtensions) + { + ".nfo", + ".xml", + ".srt", + ".vtt", + ".sub", + ".idx", + ".txt", + ".edl", + ".bif", + ".smi", + ".ttml" + }; protected BaseItem() { @@ -49,8 +59,8 @@ namespace MediaBrowser.Controller.Entities Genres = Array.Empty(); Studios = Array.Empty(); ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); - LockedFields = EmptyMetadataFieldsArray; - ImageInfos = EmptyItemImageInfoArray; + LockedFields = Array.Empty(); + ImageInfos = Array.Empty(); ProductionLocations = Array.Empty(); RemoteTrailers = Array.Empty(); ExtraIds = Array.Empty(); @@ -2452,10 +2462,8 @@ namespace MediaBrowser.Controller.Entities } var filename = System.IO.Path.GetFileNameWithoutExtension(Path); - var extensions = new List { ".nfo", ".xml", ".srt", ".vtt", ".sub", ".idx", ".txt", ".edl", ".bif", ".smi", ".ttml" }; - extensions.AddRange(SupportedImageExtensions); - return FileSystem.GetFiles(System.IO.Path.GetDirectoryName(Path), extensions.ToArray(), false, false) + return FileSystem.GetFiles(System.IO.Path.GetDirectoryName(Path), _supportedExtensions, false, false) .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.FullName).StartsWith(filename, StringComparison.OrdinalIgnoreCase)) .ToList(); } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index e49ff20baa..c056bc0b4e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -43,7 +43,7 @@ namespace MediaBrowser.Controller.Entities public Folder() { - LinkedChildren = EmptyLinkedChildArray; + LinkedChildren = Array.Empty(); } [IgnoreDataMember] diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index ff6b133982..8484938642 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -25,22 +25,10 @@ namespace MediaBrowser.Controller.Entities public DateTime DateModified { get; set; } public int Width { get; set; } + public int Height { get; set; } [IgnoreDataMember] - public bool IsLocalFile - { - get - { - if (Path != null) - { - if (Path.StartsWith("http", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - } - return true; - } - } + public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); } } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 124a943ef8..a532b5ee9e 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities.Movies { public BoxSet() { - RemoteTrailers = EmptyMediaUrlArray; + RemoteTrailers = Array.Empty(); LocalTrailerIds = Array.Empty(); RemoteTrailerIds = Array.Empty(); diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 232d116241..20c5b35219 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -21,10 +21,10 @@ namespace MediaBrowser.Controller.Entities.Movies public Movie() { - SpecialFeatureIds = new Guid[] { }; - RemoteTrailers = EmptyMediaUrlArray; - LocalTrailerIds = new Guid[] { }; - RemoteTrailerIds = new Guid[] { }; + SpecialFeatureIds = Array.Empty(); + RemoteTrailers = Array.Empty(); + LocalTrailerIds = Array.Empty(); + RemoteTrailerIds = Array.Empty(); } public Guid[] LocalTrailerIds { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 072b1d89af..fb29c07b0d 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Entities.TV { public Episode() { - RemoteTrailers = EmptyMediaUrlArray; + RemoteTrailers = Array.Empty(); LocalTrailerIds = Array.Empty(); RemoteTrailerIds = Array.Empty(); } diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index 570e9389e9..eae834f6f0 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; @@ -22,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.TV { public Series() { - RemoteTrailers = EmptyMediaUrlArray; + RemoteTrailers = Array.Empty(); LocalTrailerIds = Array.Empty(); RemoteTrailerIds = Array.Empty(); AirDays = Array.Empty(); diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 31cd429756..8379dcc090 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Entities AdditionalParts = Array.Empty(); LocalAlternateVersions = Array.Empty(); SubtitleFiles = Array.Empty(); - LinkedAlternateVersions = EmptyLinkedChildArray; + LinkedAlternateVersions = Array.Empty(); } public override bool CanDownload() diff --git a/MediaBrowser.Model/IO/IFileSystem.cs b/MediaBrowser.Model/IO/IFileSystem.cs index e0771245fd..ca99b28ca4 100644 --- a/MediaBrowser.Model/IO/IFileSystem.cs +++ b/MediaBrowser.Model/IO/IFileSystem.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Text; namespace MediaBrowser.Model.IO { @@ -177,7 +176,7 @@ namespace MediaBrowser.Model.IO /// IEnumerable GetFiles(string path, bool recursive = false); - IEnumerable GetFiles(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive); + IEnumerable GetFiles(string path, IReadOnlyList extensions, bool enableCaseSensitiveExtensions, bool recursive); /// /// Gets the file system entries. From cedf50eeeca417a65d927261ae51c055b355db8c Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 21:29:17 +0100 Subject: [PATCH 121/166] Fix the 2 new warnings --- Jellyfin.Server/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index a43c3fea5c..0ef1711d4c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -125,7 +125,7 @@ namespace Jellyfin.Server SQLitePCL.Batteries_V2.Init(); // Allow all https requests - ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; } ); + ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, appPaths); @@ -144,7 +144,6 @@ namespace Jellyfin.Server appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); await appHost.RunStartupTasks().ConfigureAwait(false); - // TODO: read input for a stop command try { From 37ea50a572e8bd00aad39c193087228a5d3a3433 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 24 Feb 2019 15:47:59 +0100 Subject: [PATCH 122/166] Reduce the amount of exceptions thrown --- Emby.Dlna/PlayTo/Device.cs | 9 +- .../Diagnostics/CommonProcess.cs | 2 +- .../Library/LibraryManager.cs | 27 +- .../LiveTv/EmbyTV/EmbyTV.cs | 4 +- .../LiveTv/EmbyTV/ItemDataProvider.cs | 26 +- .../MediaEncoder/EncodingManager.cs | 4 + MediaBrowser.Api/ApiEntryPoint.cs | 5 + .../MediaEncoding/IMediaEncoder.cs | 2 +- .../InternalMetadataFolderImageProvider.cs | 16 +- .../Encoder/EncodingUtils.cs | 6 +- .../Encoder/MediaEncoder.cs | 68 +-- .../MediaInfo/SubtitleResolver.cs | 13 +- SocketHttpListener/Net/HttpConnection.cs | 532 ++++++++++++++++++ 13 files changed, 632 insertions(+), 82 deletions(-) create mode 100644 SocketHttpListener/Net/HttpConnection.cs diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index b62c5e1d4c..0c5ddee654 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -1126,6 +1126,11 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackStart(uBaseObject mediaInfo) { + if (string.IsNullOrWhiteSpace(mediaInfo.Url)) + { + return; + } + PlaybackStart?.Invoke(this, new PlaybackStartEventArgs { MediaInfo = mediaInfo @@ -1134,8 +1139,7 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackProgress(uBaseObject mediaInfo) { - var mediaUrl = mediaInfo.Url; - if (string.IsNullOrWhiteSpace(mediaUrl)) + if (string.IsNullOrWhiteSpace(mediaInfo.Url)) { return; } @@ -1148,7 +1152,6 @@ namespace Emby.Dlna.PlayTo private void OnPlaybackStop(uBaseObject mediaInfo) { - PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs { MediaInfo = mediaInfo diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 78b22bda3f..2c4ef170dc 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/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 3c2272b566..6591d54c56 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); } } diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index fceb82ba19..f424bdf5c4 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(jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers")); - _timerProvider = new TimerManager(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; diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs index a2ac60b319..9c45ee36a2 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; @@ -32,32 +31,28 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV { if (_items == null) { + if (!File.Exists(_dataPath)) + { + return new List(); + } + Logger.LogInformation("Loading live tv data from {0}", _dataPath); _items = GetItemsFromFile(_dataPath); } + return _items.ToList(); } } private List GetItemsFromFile(string path) { - var jsonFile = path + ".json"; - - if (!File.Exists(jsonFile)) - { - return new List(); - } - try { - return _jsonSerializer.DeserializeFromFile>(jsonFile) ?? new List(); - } - catch (IOException) - { + return _jsonSerializer.DeserializeFromFile>(path); } catch (Exception ex) { - Logger.LogError(ex, "Error deserializing {jsonFile}", jsonFile); + Logger.LogError(ex, "Error deserializing {Path}", path); } return new List(); @@ -70,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/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index e68046f6d7..52d07d784d 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 GetSavedChapterImages(Video video, IDirectoryService directoryService) { var path = GetChapterImagesPath(video); + if (!Directory.Exists(path)) + { + return new List(); + } try { diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index ceff6b02e5..700cbb9439 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -172,6 +172,11 @@ namespace MediaBrowser.Api { var path = _config.ApplicationPaths.GetTranscodingTempPath(); + if (!Directory.Exists(path)) + { + return; + } + foreach (var file in _fileSystem.GetFilePaths(path, true)) { _fileSystem.DeleteFile(file); diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index d4ac3b7c37..d032a849e7 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// The input files. /// The protocol. /// System.String. - string GetInputArgument(string[] inputFiles, MediaProtocol protocol); + string GetInputArgument(IReadOnlyList inputFiles, MediaProtocol protocol); /// /// Gets the time parameter. diff --git a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs index c0706ceeb0..25a8ad5966 100644 --- a/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/InternalMetadataFolderImageProvider.cs @@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; +using Microsoft.Extensions.Logging; namespace MediaBrowser.LocalMetadata.Images { @@ -12,11 +13,16 @@ namespace MediaBrowser.LocalMetadata.Images { private readonly IServerConfigurationManager _config; private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; - public InternalMetadataFolderImageProvider(IServerConfigurationManager config, IFileSystem fileSystem) + public InternalMetadataFolderImageProvider( + IServerConfigurationManager config, + IFileSystem fileSystem, + ILogger logger) { _config = config; _fileSystem = fileSystem; + _logger = logger; } public string Name => "Internal Images"; @@ -53,12 +59,18 @@ namespace MediaBrowser.LocalMetadata.Images { var path = item.GetInternalMetadataPath(); + if (!Directory.Exists(path)) + { + return new List(); + } + try { return new LocalImageProvider(_fileSystem).GetImages(item, path, false, directoryService); } - catch (IOException) + catch (IOException ex) { + _logger.LogError(ex, "Error while getting images for {Library}", item.Name); return new List(); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs index 44e62446b2..d4aede572b 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs @@ -6,11 +6,11 @@ namespace MediaBrowser.MediaEncoding.Encoder { public static class EncodingUtils { - public static string GetInputArgument(List inputFiles, MediaProtocol protocol) + public static string GetInputArgument(IReadOnlyList inputFiles, MediaProtocol protocol) { if (protocol != MediaProtocol.File) { - var url = inputFiles.First(); + var url = inputFiles[0]; return string.Format("\"{0}\"", url); } @@ -29,7 +29,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // If there's more than one we'll need to use the concat command if (inputFiles.Count > 1) { - var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray()); + var files = string.Join("|", inputFiles.Select(NormalizePath)); return string.Format("concat:\"{0}\"", files); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 2924577888..f6ff719d58 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -334,10 +334,8 @@ namespace MediaBrowser.MediaEncoding.Encoder /// The protocol. /// System.String. /// Unrecognized InputType - public string GetInputArgument(string[] inputFiles, MediaProtocol protocol) - { - return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol); - } + public string GetInputArgument(IReadOnlyList inputFiles, MediaProtocol protocol) + => EncodingUtils.GetInputArgument(inputFiles, protocol); /// /// Gets the media info internal. @@ -354,8 +352,9 @@ namespace MediaBrowser.MediaEncoding.Encoder CancellationToken cancellationToken) { var args = extractChapters - ? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format" - : "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format"; + ? "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_chapters -show_format" + : "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format"; + args = string.Format(args, probeSizeArgument, inputPath).Trim(); var process = _processFactory.Create(new ProcessOptions { @@ -364,8 +363,10 @@ namespace MediaBrowser.MediaEncoding.Encoder // Must consume both or ffmpeg may hang due to deadlocks. See comments below. RedirectStandardOutput = true, + FileName = FFprobePath, - Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(), + Arguments = args, + IsHidden = true, ErrorDialog = false, @@ -383,36 +384,13 @@ namespace MediaBrowser.MediaEncoding.Encoder using (var processWrapper = new ProcessWrapper(process, this, _logger)) { + _logger.LogDebug("Starting ffprobe with args {Args}", args); StartProcess(processWrapper); + InternalMediaInfoResult result; try { - //process.BeginErrorReadLine(); - - var result = await _jsonSerializer.DeserializeFromStreamAsync(process.StandardOutput.BaseStream).ConfigureAwait(false); - - if (result == null || (result.streams == null && result.format == null)) - { - throw new Exception("ffprobe failed - streams and format are both null."); - } - - if (result.streams != null) - { - // Normalize aspect ratio if invalid - foreach (var stream in result.streams) - { - if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.display_aspect_ratio = string.Empty; - } - if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) - { - stream.sample_aspect_ratio = string.Empty; - } - } - } - - return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); + result = await _jsonSerializer.DeserializeFromStreamAsync(process.StandardOutput.BaseStream).ConfigureAwait(false); } catch { @@ -420,6 +398,30 @@ namespace MediaBrowser.MediaEncoding.Encoder throw; } + + if (result == null || (result.streams == null && result.format == null)) + { + throw new Exception("ffprobe failed - streams and format are both null."); + } + + if (result.streams != null) + { + // Normalize aspect ratio if invalid + foreach (var stream in result.streams) + { + if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) + { + stream.display_aspect_ratio = string.Empty; + } + + if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase)) + { + stream.sample_aspect_ratio = string.Empty; + } + } + } + + return new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol); } } diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs index cd026b39b8..8195591e17 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs @@ -16,7 +16,7 @@ namespace MediaBrowser.Providers.MediaInfo private readonly ILocalizationManager _localization; private readonly IFileSystem _fileSystem; - private string[] SubtitleExtensions = new[] + private static readonly HashSet SubtitleExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) { ".srt", ".ssa", @@ -49,9 +49,16 @@ namespace MediaBrowser.Providers.MediaInfo startIndex += streams.Count; + string folder = video.GetInternalMetadataPath(); + + if (!Directory.Exists(folder)) + { + return streams; + } + try { - AddExternalSubtitleStreams(streams, video.GetInternalMetadataPath(), video.Path, startIndex, directoryService, clearCache); + AddExternalSubtitleStreams(streams, folder, video.Path, startIndex, directoryService, clearCache); } catch (IOException) { @@ -105,7 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo { var extension = Path.GetExtension(fullName); - if (!SubtitleExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) + if (!SubtitleExtensions.Contains(extension)) { continue; } diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs new file mode 100644 index 0000000000..5beea5f221 --- /dev/null +++ b/SocketHttpListener/Net/HttpConnection.cs @@ -0,0 +1,532 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Model.Cryptography; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.System; +using Microsoft.Extensions.Logging; +namespace SocketHttpListener.Net +{ + sealed class HttpConnection + { + private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); + const int BufferSize = 8192; + Socket _socket; + Stream _stream; + HttpEndPointListener _epl; + MemoryStream _memoryStream; + byte[] _buffer; + HttpListenerContext _context; + StringBuilder _currentLine; + ListenerPrefix _prefix; + HttpRequestStream _requestStream; + HttpResponseStream _responseStream; + bool _chunked; + int _reuses; + bool _contextBound; + bool secure; + IPEndPoint local_ep; + HttpListener _lastListener; + X509Certificate cert; + SslStream ssl_stream; + + private readonly ILogger _logger; + private readonly ICryptoProvider _cryptoProvider; + private readonly IStreamHelper _streamHelper; + private readonly IFileSystem _fileSystem; + private readonly IEnvironmentInfo _environment; + + public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, + X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem, + IEnvironmentInfo environment) + { + _logger = logger; + this._socket = socket; + this._epl = epl; + this.secure = secure; + this.cert = cert; + _cryptoProvider = cryptoProvider; + _streamHelper = streamHelper; + _fileSystem = fileSystem; + _environment = environment; + + if (secure == false) + { + _stream = new SocketStream(_socket, false); + } + else + { + ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) => + { + if (c == null) + { + return true; + } + + //var c2 = c as X509Certificate2; + //if (c2 == null) + //{ + // c2 = new X509Certificate2(c.GetRawCertData()); + //} + + //_clientCert = c2; + //_clientCertErrors = new int[] { (int)e }; + return true; + }); + + _stream = ssl_stream; + } + } + + public Stream Stream => _stream; + + public async Task Init() + { + if (ssl_stream != null) + { + var enableAsync = true; + if (enableAsync) + { + await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false); + } + else + { + ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); + } + } + + InitInternal(); + } + + private void InitInternal() + { + _contextBound = false; + _requestStream = null; + _responseStream = null; + _prefix = null; + _chunked = false; + _memoryStream = new MemoryStream(); + _position = 0; + _inputState = InputState.RequestLine; + _lineState = LineState.None; + _context = new HttpListenerContext(this); + } + + public bool IsClosed => (_socket == null); + + public int Reuses => _reuses; + + public IPEndPoint LocalEndPoint + { + get + { + if (local_ep != null) + return local_ep; + + local_ep = (IPEndPoint)_socket.LocalEndPoint; + return local_ep; + } + } + + public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; + + public bool IsSecure => secure; + + public ListenerPrefix Prefix + { + get => _prefix; + set => _prefix = value; + } + + private void OnTimeout(object unused) + { + //_logger.LogInformation("HttpConnection timer fired"); + CloseSocket(); + Unbind(); + } + + public void BeginReadRequest() + { + if (_buffer == null) + { + _buffer = new byte[BufferSize]; + } + + try + { + _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); + } + catch + { + CloseSocket(); + Unbind(); + } + } + + public HttpRequestStream GetRequestStream(bool chunked, long contentlength) + { + if (_requestStream == null) + { + byte[] buffer = _memoryStream.GetBuffer(); + int length = (int)_memoryStream.Length; + _memoryStream = null; + if (chunked) + { + _chunked = true; + //_context.Response.SendChunked = true; + _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position); + } + else + { + _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength); + } + } + return _requestStream; + } + + public HttpResponseStream GetResponseStream(bool isExpect100Continue = false) + { + // TODO: can we get this _stream before reading the input? + if (_responseStream == null) + { + var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure; + + _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger); + } + return _responseStream; + } + + private static void OnRead(IAsyncResult ares) + { + var cnc = (HttpConnection)ares.AsyncState; + cnc.OnReadInternal(ares); + } + + private void OnReadInternal(IAsyncResult ares) + { + int nread = -1; + try + { + nread = _stream.EndRead(ares); + _memoryStream.Write(_buffer, 0, nread); + if (_memoryStream.Length > 32768) + { + SendError("Bad Request", 400); + Close(true); + return; + } + } + catch + { + if (_memoryStream != null && _memoryStream.Length > 0) + { + SendError(); + } + + if (_socket != null) + { + CloseSocket(); + Unbind(); + } + return; + } + + if (nread == 0) + { + CloseSocket(); + Unbind(); + return; + } + + if (ProcessInput(_memoryStream)) + { + if (!_context.HaveError) + _context.Request.FinishInitialization(); + + if (_context.HaveError) + { + SendError(); + Close(true); + return; + } + + if (!_epl.BindContext(_context)) + { + const int NotFoundErrorCode = 404; + SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode); + Close(true); + return; + } + HttpListener listener = _epl.Listener; + if (_lastListener != listener) + { + RemoveConnection(); + listener.AddConnection(this); + _lastListener = listener; + } + + _contextBound = true; + listener.RegisterContext(_context); + return; + } + _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); + } + + private void RemoveConnection() + { + if (_lastListener == null) + _epl.RemoveConnection(this); + else + _lastListener.RemoveConnection(this); + } + + private enum InputState + { + RequestLine, + Headers + } + + private enum LineState + { + None, + CR, + LF + } + + InputState _inputState = InputState.RequestLine; + LineState _lineState = LineState.None; + int _position; + + // true -> done processing + // false -> need more input + private bool ProcessInput(MemoryStream ms) + { + byte[] buffer = ms.GetBuffer(); + int len = (int)ms.Length; + int used = 0; + string line; + + while (true) + { + if (_context.HaveError) + return true; + + if (_position >= len) + break; + + try + { + line = ReadLine(buffer, _position, len - _position, ref used); + _position += used; + } + catch + { + _context.ErrorMessage = "Bad request"; + _context.ErrorStatus = 400; + return true; + } + + if (line == null) + break; + + if (line == "") + { + if (_inputState == InputState.RequestLine) + continue; + _currentLine = null; + ms = null; + return true; + } + + if (_inputState == InputState.RequestLine) + { + _context.Request.SetRequestLine(line); + _inputState = InputState.Headers; + } + else + { + try + { + _context.Request.AddHeader(line); + } + catch (Exception e) + { + _context.ErrorMessage = e.Message; + _context.ErrorStatus = 400; + return true; + } + } + } + + if (used == len) + { + ms.SetLength(0); + _position = 0; + } + return false; + } + + private string ReadLine(byte[] buffer, int offset, int len, ref int used) + { + if (_currentLine == null) + _currentLine = new StringBuilder(128); + int last = offset + len; + used = 0; + for (int i = offset; i < last && _lineState != LineState.LF; i++) + { + used++; + byte b = buffer[i]; + if (b == 13) + { + _lineState = LineState.CR; + } + else if (b == 10) + { + _lineState = LineState.LF; + } + else + { + _currentLine.Append((char)b); + } + } + + string result = null; + if (_lineState == LineState.LF) + { + _lineState = LineState.None; + result = _currentLine.ToString(); + _currentLine.Length = 0; + } + + return result; + } + + public void SendError(string msg, int status) + { + try + { + HttpListenerResponse response = _context.Response; + response.StatusCode = status; + response.ContentType = "text/html"; + string description = HttpStatusDescription.Get(status); + string str; + if (msg != null) + str = string.Format("

{0} ({1})

", description, msg); + else + str = string.Format("

{0}

", description); + + byte[] error = Encoding.UTF8.GetBytes(str); + response.Close(error, false); + } + catch + { + // response was already closed + } + } + + public void SendError() + { + SendError(_context.ErrorMessage, _context.ErrorStatus); + } + + private void Unbind() + { + if (_contextBound) + { + _epl.UnbindContext(_context); + _contextBound = false; + } + } + + public void Close() + { + Close(false); + } + + private void CloseSocket() + { + if (_socket == null) + return; + + try + { + _socket.Close(); + } + catch { } + finally + { + _socket = null; + } + + RemoveConnection(); + } + + internal void Close(bool force) + { + if (_socket != null) + { + Stream st = GetResponseStream(); + if (st != null) + st.Close(); + + _responseStream = null; + } + + if (_socket != null) + { + force |= !_context.Request.KeepAlive; + if (!force) + { + force = string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase); + } + + if (!force && _context.Request.FlushInput()) + { + if (_chunked && _context.Response.ForceCloseChunked == false) + { + // Don't close. Keep working. + _reuses++; + Unbind(); + InitInternal(); + BeginReadRequest(); + return; + } + + _reuses++; + Unbind(); + InitInternal(); + BeginReadRequest(); + return; + } + + Socket s = _socket; + _socket = null; + try + { + s?.Shutdown(SocketShutdown.Both); + } + catch + { + } + finally + { + try + { + s?.Close(); + } + catch { } + } + Unbind(); + RemoveConnection(); + return; + } + } + } +} From ab9859ecefa7ec28b38cc807226b3df31fdf3e93 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 13:12:37 +0100 Subject: [PATCH 123/166] Address comment --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index f6ff719d58..94beda3dbb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -390,7 +390,8 @@ namespace MediaBrowser.MediaEncoding.Encoder InternalMediaInfoResult result; try { - result = await _jsonSerializer.DeserializeFromStreamAsync(process.StandardOutput.BaseStream).ConfigureAwait(false); + result = await _jsonSerializer.DeserializeFromStreamAsync( + process.StandardOutput.BaseStream).ConfigureAwait(false); } catch { From 620d7b560d0b72f532d96a5b459f14fd4ad3044a Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 28 Feb 2019 22:33:20 +0100 Subject: [PATCH 124/166] Fail on warnings for Jellyfin.Server --- .../Activity/ActivityLogEntryPoint.cs | 181 +++++++----------- .../Emby.Server.Implementations.csproj | 15 ++ .../SocketSharp/HttpPostedFile.cs | 44 ++--- Jellyfin.Server/Jellyfin.Server.csproj | 7 +- jellyfin.ruleset | 5 + 5 files changed, 118 insertions(+), 134 deletions(-) diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs index 739f687678..98cd97c318 100644 --- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs +++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs @@ -30,13 +30,10 @@ namespace Emby.Server.Implementations.Activity public class ActivityLogEntryPoint : IServerEntryPoint { private readonly IInstallationManager _installationManager; - - //private readonly ILogger _logger; private readonly ISessionManager _sessionManager; private readonly ITaskManager _taskManager; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; - private readonly ILibraryManager _libraryManager; private readonly ISubtitleManager _subManager; private readonly IUserManager _userManager; @@ -61,41 +58,37 @@ namespace Emby.Server.Implementations.Activity public Task RunAsync() { - _taskManager.TaskCompleted += _taskManager_TaskCompleted; + _taskManager.TaskCompleted += OnTaskCompleted; - _installationManager.PluginInstalled += _installationManager_PluginInstalled; - _installationManager.PluginUninstalled += _installationManager_PluginUninstalled; - _installationManager.PluginUpdated += _installationManager_PluginUpdated; - _installationManager.PackageInstallationFailed += _installationManager_PackageInstallationFailed; + _installationManager.PluginInstalled += OnPluginInstalled; + _installationManager.PluginUninstalled += OnPluginUninstalled; + _installationManager.PluginUpdated += OnPluginUpdated; + _installationManager.PackageInstallationFailed += OnPackageInstallationFailed; - _sessionManager.SessionStarted += _sessionManager_SessionStarted; - _sessionManager.AuthenticationFailed += _sessionManager_AuthenticationFailed; - _sessionManager.AuthenticationSucceeded += _sessionManager_AuthenticationSucceeded; - _sessionManager.SessionEnded += _sessionManager_SessionEnded; + _sessionManager.SessionStarted += OnSessionStarted; + _sessionManager.AuthenticationFailed += OnAuthenticationFailed; + _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded; + _sessionManager.SessionEnded += OnSessionEnded; - _sessionManager.PlaybackStart += _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped += _sessionManager_PlaybackStopped; + _sessionManager.PlaybackStart += OnPlaybackStart; + _sessionManager.PlaybackStopped += OnPlaybackStopped; - //_subManager.SubtitlesDownloaded += _subManager_SubtitlesDownloaded; - _subManager.SubtitleDownloadFailure += _subManager_SubtitleDownloadFailure; + _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure; - _userManager.UserCreated += _userManager_UserCreated; - _userManager.UserPasswordChanged += _userManager_UserPasswordChanged; - _userManager.UserDeleted += _userManager_UserDeleted; - _userManager.UserPolicyUpdated += _userManager_UserPolicyUpdated; - _userManager.UserLockedOut += _userManager_UserLockedOut; + _userManager.UserCreated += OnUserCreated; + _userManager.UserPasswordChanged += OnUserPasswordChanged; + _userManager.UserDeleted += OnUserDeleted; + _userManager.UserPolicyUpdated += OnUserPolicyUpdated; + _userManager.UserLockedOut += OnUserLockedOut; - //_config.ConfigurationUpdated += _config_ConfigurationUpdated; - //_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; + _deviceManager.CameraImageUploaded += OnCameraImageUploaded; - _deviceManager.CameraImageUploaded += _deviceManager_CameraImageUploaded; - - _appHost.ApplicationUpdated += _appHost_ApplicationUpdated; + _appHost.ApplicationUpdated += OnApplicationUpdated; return Task.CompletedTask; } - void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs e) + private void OnCameraImageUploaded(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -104,7 +97,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserLockedOut(object sender, GenericEventArgs e) + private void OnUserLockedOut(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -114,7 +107,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) + private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -125,7 +118,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_PlaybackStopped(object sender, PlaybackStopEventArgs e) + private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e) { var item = e.MediaInfo; @@ -146,7 +139,7 @@ namespace Emby.Server.Implementations.Activity return; } - var user = e.Users.First(); + var user = e.Users[0]; CreateLogEntry(new ActivityLogEntry { @@ -156,7 +149,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_PlaybackStart(object sender, PlaybackProgressEventArgs e) + private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e) { var item = e.MediaInfo; @@ -232,7 +225,7 @@ namespace Emby.Server.Implementations.Activity return null; } - void _sessionManager_SessionEnded(object sender, SessionEventArgs e) + private void OnSessionEnded(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; @@ -258,7 +251,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_AuthenticationSucceeded(object sender, GenericEventArgs e) + private void OnAuthenticationSucceeded(object sender, GenericEventArgs e) { var user = e.Argument.User; @@ -271,7 +264,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _sessionManager_AuthenticationFailed(object sender, GenericEventArgs e) + private void OnAuthenticationFailed(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -282,7 +275,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _appHost_ApplicationUpdated(object sender, GenericEventArgs e) + private void OnApplicationUpdated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -292,25 +285,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("MessageNamedServerConfigurationUpdatedWithValue"), e.Key), - Type = "NamedConfigurationUpdated" - }); - } - - void _config_ConfigurationUpdated(object sender, EventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = _localization.GetLocalizedString("MessageServerConfigurationUpdated"), - Type = "ServerConfigurationUpdated" - }); - } - - void _userManager_UserPolicyUpdated(object sender, GenericEventArgs e) + private void OnUserPolicyUpdated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -320,7 +295,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserDeleted(object sender, GenericEventArgs e) + private void OnUserDeleted(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -329,7 +304,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserPasswordChanged(object sender, GenericEventArgs e) + private void OnUserPasswordChanged(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -339,7 +314,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _userManager_UserCreated(object sender, GenericEventArgs e) + private void OnUserCreated(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -349,18 +324,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _subManager_SubtitlesDownloaded(object sender, SubtitleDownloadEventArgs e) - { - CreateLogEntry(new ActivityLogEntry - { - Name = string.Format(_localization.GetLocalizedString("SubtitlesDownloadedForItem"), Notifications.Notifications.GetItemName(e.Item)), - Type = "SubtitlesDownloaded", - ItemId = e.Item.Id.ToString("N"), - ShortOverview = string.Format(_localization.GetLocalizedString("ProviderValue"), e.Provider) - }); - } - - void _sessionManager_SessionStarted(object sender, SessionEventArgs e) + private void OnSessionStarted(object sender, SessionEventArgs e) { string name; var session = e.SessionInfo; @@ -386,7 +350,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginUpdated(object sender, GenericEventArgs> e) + private void OnPluginUpdated(object sender, GenericEventArgs> e) { CreateLogEntry(new ActivityLogEntry { @@ -397,7 +361,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginUninstalled(object sender, GenericEventArgs e) + private void OnPluginUninstalled(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -406,7 +370,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PluginInstalled(object sender, GenericEventArgs e) + private void OnPluginInstalled(object sender, GenericEventArgs e) { CreateLogEntry(new ActivityLogEntry { @@ -416,7 +380,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _installationManager_PackageInstallationFailed(object sender, InstallationFailedEventArgs e) + private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e) { var installationInfo = e.InstallationInfo; @@ -429,7 +393,7 @@ namespace Emby.Server.Implementations.Activity }); } - void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e) + private void OnTaskCompleted(object sender, TaskCompletionEventArgs e) { var result = e.Result; var task = e.Task; @@ -468,48 +432,36 @@ namespace Emby.Server.Implementations.Activity } private void CreateLogEntry(ActivityLogEntry entry) - { - try - { - _activityManager.Create(entry); - } - catch - { - // Logged at lower levels - } - } + => _activityManager.Create(entry); public void Dispose() { - _taskManager.TaskCompleted -= _taskManager_TaskCompleted; + _taskManager.TaskCompleted -= OnTaskCompleted; - _installationManager.PluginInstalled -= _installationManager_PluginInstalled; - _installationManager.PluginUninstalled -= _installationManager_PluginUninstalled; - _installationManager.PluginUpdated -= _installationManager_PluginUpdated; - _installationManager.PackageInstallationFailed -= _installationManager_PackageInstallationFailed; + _installationManager.PluginInstalled -= OnPluginInstalled; + _installationManager.PluginUninstalled -= OnPluginUninstalled; + _installationManager.PluginUpdated -= OnPluginUpdated; + _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed; - _sessionManager.SessionStarted -= _sessionManager_SessionStarted; - _sessionManager.AuthenticationFailed -= _sessionManager_AuthenticationFailed; - _sessionManager.AuthenticationSucceeded -= _sessionManager_AuthenticationSucceeded; - _sessionManager.SessionEnded -= _sessionManager_SessionEnded; + _sessionManager.SessionStarted -= OnSessionStarted; + _sessionManager.AuthenticationFailed -= OnAuthenticationFailed; + _sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded; + _sessionManager.SessionEnded -= OnSessionEnded; - _sessionManager.PlaybackStart -= _sessionManager_PlaybackStart; - _sessionManager.PlaybackStopped -= _sessionManager_PlaybackStopped; + _sessionManager.PlaybackStart -= OnPlaybackStart; + _sessionManager.PlaybackStopped -= OnPlaybackStopped; - _subManager.SubtitleDownloadFailure -= _subManager_SubtitleDownloadFailure; + _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure; - _userManager.UserCreated -= _userManager_UserCreated; - _userManager.UserPasswordChanged -= _userManager_UserPasswordChanged; - _userManager.UserDeleted -= _userManager_UserDeleted; - _userManager.UserPolicyUpdated -= _userManager_UserPolicyUpdated; - _userManager.UserLockedOut -= _userManager_UserLockedOut; + _userManager.UserCreated -= OnUserCreated; + _userManager.UserPasswordChanged -= OnUserPasswordChanged; + _userManager.UserDeleted -= OnUserDeleted; + _userManager.UserPolicyUpdated -= OnUserPolicyUpdated; + _userManager.UserLockedOut -= OnUserLockedOut; - _config.ConfigurationUpdated -= _config_ConfigurationUpdated; - _config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated; + _deviceManager.CameraImageUploaded -= OnCameraImageUploaded; - _deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded; - - _appHost.ApplicationUpdated -= _appHost_ApplicationUpdated; + _appHost.ApplicationUpdated -= OnApplicationUpdated; } /// @@ -531,6 +483,7 @@ namespace Emby.Server.Implementations.Activity values.Add(CreateValueString(years, "year")); days = days % DaysInYear; } + // Number of months if (days >= DaysInMonth) { @@ -538,25 +491,39 @@ namespace Emby.Server.Implementations.Activity values.Add(CreateValueString(months, "month")); days = days % DaysInMonth; } + // Number of days if (days >= 1) + { values.Add(CreateValueString(days, "day")); + } + // Number of hours if (span.Hours >= 1) + { values.Add(CreateValueString(span.Hours, "hour")); + } // Number of minutes if (span.Minutes >= 1) + { values.Add(CreateValueString(span.Minutes, "minute")); + } + // Number of seconds (include when 0 if no other components included) if (span.Seconds >= 1 || values.Count == 0) + { values.Add(CreateValueString(span.Seconds, "second")); + } // Combine values into string var builder = new StringBuilder(); for (int i = 0; i < values.Count; i++) { if (builder.Length > 0) + { builder.Append(i == values.Count - 1 ? " and " : ", "); + } + builder.Append(values[i]); } // Return result diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 39e9ed3756..3cbd2792d9 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -47,6 +47,21 @@ false + + true + + + + + + + + + + + ../jellyfin.ruleset + + diff --git a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs index f38ed848ee..95b7912fbb 100644 --- a/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs +++ b/Emby.Server.Implementations/SocketSharp/HttpPostedFile.cs @@ -63,6 +63,28 @@ public sealed class HttpPostedFile : IDisposable _position = offset; } + 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); + } + } + public override void Flush() { } @@ -178,27 +200,5 @@ public sealed class HttpPostedFile : IDisposable { 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/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index bd670df527..9346a2d254 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -12,7 +12,8 @@ latest - SA1600;CS1591 + SA1600;SA1601;CS1591 + true @@ -23,10 +24,6 @@ - - true - - diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 4381349ca6..0a04b4c557 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -1,6 +1,11 @@ + + + + + From 8a680ae324fbc97280396d0f49a60fcdd05f36f6 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 7 Mar 2019 22:00:28 +0100 Subject: [PATCH 125/166] Update to renamed DownloadGitHubRelease task (Microsoft/azure-pipelines-tasks#9481) --- .ci/azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index e5845c0efa..4cd19dc838 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -156,7 +156,7 @@ jobs: overWrite: true # Optional flattenFolders: true # Optional - - task: DownloadGitHubReleases@0 + - task: DownloadGitHubRelease@0 displayName: Download ABI compatibility check tool from GitHub inputs: connection: Jellyfin GitHub From d014a1dc1d6e2c9f2cd34dff768a94521f2a0cb9 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 22:16:27 +0100 Subject: [PATCH 126/166] Remove file added in #996 --- SocketHttpListener/Net/HttpConnection.cs | 532 ----------------------- 1 file changed, 532 deletions(-) delete mode 100644 SocketHttpListener/Net/HttpConnection.cs diff --git a/SocketHttpListener/Net/HttpConnection.cs b/SocketHttpListener/Net/HttpConnection.cs deleted file mode 100644 index 5beea5f221..0000000000 --- a/SocketHttpListener/Net/HttpConnection.cs +++ /dev/null @@ -1,532 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Security; -using System.Net.Sockets; -using System.Security.Authentication; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Model.Cryptography; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; -using Microsoft.Extensions.Logging; -namespace SocketHttpListener.Net -{ - sealed class HttpConnection - { - private static AsyncCallback s_onreadCallback = new AsyncCallback(OnRead); - const int BufferSize = 8192; - Socket _socket; - Stream _stream; - HttpEndPointListener _epl; - MemoryStream _memoryStream; - byte[] _buffer; - HttpListenerContext _context; - StringBuilder _currentLine; - ListenerPrefix _prefix; - HttpRequestStream _requestStream; - HttpResponseStream _responseStream; - bool _chunked; - int _reuses; - bool _contextBound; - bool secure; - IPEndPoint local_ep; - HttpListener _lastListener; - X509Certificate cert; - SslStream ssl_stream; - - private readonly ILogger _logger; - private readonly ICryptoProvider _cryptoProvider; - private readonly IStreamHelper _streamHelper; - private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environment; - - public HttpConnection(ILogger logger, Socket socket, HttpEndPointListener epl, bool secure, - X509Certificate cert, ICryptoProvider cryptoProvider, IStreamHelper streamHelper, IFileSystem fileSystem, - IEnvironmentInfo environment) - { - _logger = logger; - this._socket = socket; - this._epl = epl; - this.secure = secure; - this.cert = cert; - _cryptoProvider = cryptoProvider; - _streamHelper = streamHelper; - _fileSystem = fileSystem; - _environment = environment; - - if (secure == false) - { - _stream = new SocketStream(_socket, false); - } - else - { - ssl_stream = new SslStream(new SocketStream(_socket, false), false, (t, c, ch, e) => - { - if (c == null) - { - return true; - } - - //var c2 = c as X509Certificate2; - //if (c2 == null) - //{ - // c2 = new X509Certificate2(c.GetRawCertData()); - //} - - //_clientCert = c2; - //_clientCertErrors = new int[] { (int)e }; - return true; - }); - - _stream = ssl_stream; - } - } - - public Stream Stream => _stream; - - public async Task Init() - { - if (ssl_stream != null) - { - var enableAsync = true; - if (enableAsync) - { - await ssl_stream.AuthenticateAsServerAsync(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false).ConfigureAwait(false); - } - else - { - ssl_stream.AuthenticateAsServer(cert, false, (SslProtocols)ServicePointManager.SecurityProtocol, false); - } - } - - InitInternal(); - } - - private void InitInternal() - { - _contextBound = false; - _requestStream = null; - _responseStream = null; - _prefix = null; - _chunked = false; - _memoryStream = new MemoryStream(); - _position = 0; - _inputState = InputState.RequestLine; - _lineState = LineState.None; - _context = new HttpListenerContext(this); - } - - public bool IsClosed => (_socket == null); - - public int Reuses => _reuses; - - public IPEndPoint LocalEndPoint - { - get - { - if (local_ep != null) - return local_ep; - - local_ep = (IPEndPoint)_socket.LocalEndPoint; - return local_ep; - } - } - - public IPEndPoint RemoteEndPoint => _socket.RemoteEndPoint as IPEndPoint; - - public bool IsSecure => secure; - - public ListenerPrefix Prefix - { - get => _prefix; - set => _prefix = value; - } - - private void OnTimeout(object unused) - { - //_logger.LogInformation("HttpConnection timer fired"); - CloseSocket(); - Unbind(); - } - - public void BeginReadRequest() - { - if (_buffer == null) - { - _buffer = new byte[BufferSize]; - } - - try - { - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - catch - { - CloseSocket(); - Unbind(); - } - } - - public HttpRequestStream GetRequestStream(bool chunked, long contentlength) - { - if (_requestStream == null) - { - byte[] buffer = _memoryStream.GetBuffer(); - int length = (int)_memoryStream.Length; - _memoryStream = null; - if (chunked) - { - _chunked = true; - //_context.Response.SendChunked = true; - _requestStream = new ChunkedInputStream(_context, _stream, buffer, _position, length - _position); - } - else - { - _requestStream = new HttpRequestStream(_stream, buffer, _position, length - _position, contentlength); - } - } - return _requestStream; - } - - public HttpResponseStream GetResponseStream(bool isExpect100Continue = false) - { - // TODO: can we get this _stream before reading the input? - if (_responseStream == null) - { - var supportsDirectSocketAccess = !_context.Response.SendChunked && !isExpect100Continue && !secure; - - _responseStream = new HttpResponseStream(_stream, _context.Response, false, _streamHelper, _socket, supportsDirectSocketAccess, _environment, _fileSystem, _logger); - } - return _responseStream; - } - - private static void OnRead(IAsyncResult ares) - { - var cnc = (HttpConnection)ares.AsyncState; - cnc.OnReadInternal(ares); - } - - private void OnReadInternal(IAsyncResult ares) - { - int nread = -1; - try - { - nread = _stream.EndRead(ares); - _memoryStream.Write(_buffer, 0, nread); - if (_memoryStream.Length > 32768) - { - SendError("Bad Request", 400); - Close(true); - return; - } - } - catch - { - if (_memoryStream != null && _memoryStream.Length > 0) - { - SendError(); - } - - if (_socket != null) - { - CloseSocket(); - Unbind(); - } - return; - } - - if (nread == 0) - { - CloseSocket(); - Unbind(); - return; - } - - if (ProcessInput(_memoryStream)) - { - if (!_context.HaveError) - _context.Request.FinishInitialization(); - - if (_context.HaveError) - { - SendError(); - Close(true); - return; - } - - if (!_epl.BindContext(_context)) - { - const int NotFoundErrorCode = 404; - SendError(HttpStatusDescription.Get(NotFoundErrorCode), NotFoundErrorCode); - Close(true); - return; - } - HttpListener listener = _epl.Listener; - if (_lastListener != listener) - { - RemoveConnection(); - listener.AddConnection(this); - _lastListener = listener; - } - - _contextBound = true; - listener.RegisterContext(_context); - return; - } - _stream.BeginRead(_buffer, 0, BufferSize, s_onreadCallback, this); - } - - private void RemoveConnection() - { - if (_lastListener == null) - _epl.RemoveConnection(this); - else - _lastListener.RemoveConnection(this); - } - - private enum InputState - { - RequestLine, - Headers - } - - private enum LineState - { - None, - CR, - LF - } - - InputState _inputState = InputState.RequestLine; - LineState _lineState = LineState.None; - int _position; - - // true -> done processing - // false -> need more input - private bool ProcessInput(MemoryStream ms) - { - byte[] buffer = ms.GetBuffer(); - int len = (int)ms.Length; - int used = 0; - string line; - - while (true) - { - if (_context.HaveError) - return true; - - if (_position >= len) - break; - - try - { - line = ReadLine(buffer, _position, len - _position, ref used); - _position += used; - } - catch - { - _context.ErrorMessage = "Bad request"; - _context.ErrorStatus = 400; - return true; - } - - if (line == null) - break; - - if (line == "") - { - if (_inputState == InputState.RequestLine) - continue; - _currentLine = null; - ms = null; - return true; - } - - if (_inputState == InputState.RequestLine) - { - _context.Request.SetRequestLine(line); - _inputState = InputState.Headers; - } - else - { - try - { - _context.Request.AddHeader(line); - } - catch (Exception e) - { - _context.ErrorMessage = e.Message; - _context.ErrorStatus = 400; - return true; - } - } - } - - if (used == len) - { - ms.SetLength(0); - _position = 0; - } - return false; - } - - private string ReadLine(byte[] buffer, int offset, int len, ref int used) - { - if (_currentLine == null) - _currentLine = new StringBuilder(128); - int last = offset + len; - used = 0; - for (int i = offset; i < last && _lineState != LineState.LF; i++) - { - used++; - byte b = buffer[i]; - if (b == 13) - { - _lineState = LineState.CR; - } - else if (b == 10) - { - _lineState = LineState.LF; - } - else - { - _currentLine.Append((char)b); - } - } - - string result = null; - if (_lineState == LineState.LF) - { - _lineState = LineState.None; - result = _currentLine.ToString(); - _currentLine.Length = 0; - } - - return result; - } - - public void SendError(string msg, int status) - { - try - { - HttpListenerResponse response = _context.Response; - response.StatusCode = status; - response.ContentType = "text/html"; - string description = HttpStatusDescription.Get(status); - string str; - if (msg != null) - str = string.Format("

{0} ({1})

", description, msg); - else - str = string.Format("

{0}

", description); - - byte[] error = Encoding.UTF8.GetBytes(str); - response.Close(error, false); - } - catch - { - // response was already closed - } - } - - public void SendError() - { - SendError(_context.ErrorMessage, _context.ErrorStatus); - } - - private void Unbind() - { - if (_contextBound) - { - _epl.UnbindContext(_context); - _contextBound = false; - } - } - - public void Close() - { - Close(false); - } - - private void CloseSocket() - { - if (_socket == null) - return; - - try - { - _socket.Close(); - } - catch { } - finally - { - _socket = null; - } - - RemoveConnection(); - } - - internal void Close(bool force) - { - if (_socket != null) - { - Stream st = GetResponseStream(); - if (st != null) - st.Close(); - - _responseStream = null; - } - - if (_socket != null) - { - force |= !_context.Request.KeepAlive; - if (!force) - { - force = string.Equals(_context.Response.Headers["connection"], "close", StringComparison.OrdinalIgnoreCase); - } - - if (!force && _context.Request.FlushInput()) - { - if (_chunked && _context.Response.ForceCloseChunked == false) - { - // Don't close. Keep working. - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - _reuses++; - Unbind(); - InitInternal(); - BeginReadRequest(); - return; - } - - Socket s = _socket; - _socket = null; - try - { - s?.Shutdown(SocketShutdown.Both); - } - catch - { - } - finally - { - try - { - s?.Close(); - } - catch { } - } - Unbind(); - RemoveConnection(); - return; - } - } - } -} From 3fa43a1e08a719e65ed38a57b556be0c0edacaef Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 22:26:23 +0100 Subject: [PATCH 127/166] Don't set status code if response is closed --- .../HttpServer/HttpListenerHost.cs | 4 +- .../Services/ServiceExec.cs | 2 +- .../SocketSharp/WebSocketSharpListener.cs | 5 ++- .../SocketSharp/WebSocketSharpRequest.cs | 7 +--- .../SocketSharp/WebSocketSharpResponse.cs | 37 +++++-------------- MediaBrowser.Model/Services/IRequest.cs | 14 +------ 6 files changed, 19 insertions(+), 50 deletions(-) diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index d1b1b5b242..eab755cefb 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -225,7 +225,7 @@ namespace Emby.Server.Implementations.HttpServer var httpRes = httpReq.Response; - if (httpRes.IsClosed) + if (httpRes.OriginalResponse.HasStarted) { return; } @@ -595,8 +595,6 @@ namespace Emby.Server.Implementations.HttpServer } finally { - // TODO response closes automatically after the handler is done, but some functions rely on knowing if it's closed or not - httpRes.IsClosed = true; stopWatch.Stop(); var elapsed = stopWatch.Elapsed; if (elapsed.TotalMilliseconds > 500) diff --git a/Emby.Server.Implementations/Services/ServiceExec.cs b/Emby.Server.Implementations/Services/ServiceExec.cs index 79f5c59e65..38952628d8 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(null); } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs index 2df826957a..dd313b3363 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpListener.cs @@ -90,7 +90,10 @@ namespace Emby.Server.Implementations.SocketSharp catch (Exception ex) { _logger.LogError(ex, "AcceptWebSocketAsync error"); - ctx.Response.StatusCode = 500; + if (!ctx.Response.HasStarted) + { + ctx.Response.StatusCode = 500; + } } } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs index bc002dc4ce..2d3ec3c8ea 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs @@ -20,22 +20,19 @@ namespace Emby.Server.Implementations.SocketSharp public partial class WebSocketSharpRequest : IHttpRequest { private readonly HttpRequest request; - private readonly IResponse response; public WebSocketSharpRequest(HttpRequest httpContext, HttpResponse response, string operationName, ILogger logger) { this.OperationName = operationName; this.request = httpContext; - this.response = new WebSocketSharpResponse(logger, response, this); + this.Response = new WebSocketSharpResponse(logger, response); // HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes[0]); } public HttpRequest HttpRequest => request; - public IResponse Response => response; - - public IResponse HttpResponse => response; + public IResponse Response { get; } public string OperationName { get; set; } diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs index a7e3e6c700..0f67eaa622 100644 --- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs +++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpResponse.cs @@ -16,38 +16,28 @@ namespace Emby.Server.Implementations.SocketSharp { private readonly ILogger _logger; - private readonly HttpResponse _response; - - public WebSocketSharpResponse(ILogger logger, HttpResponse response, IRequest request) + public WebSocketSharpResponse(ILogger logger, HttpResponse response) { _logger = logger; - this._response = response; - Items = new Dictionary(); - Request = request; + OriginalResponse = response; } - public IRequest Request { get; private set; } - - public Dictionary Items { get; private set; } - - public object OriginalResponse => _response; + public HttpResponse OriginalResponse { get; } public int StatusCode { - get => this._response.StatusCode; - set => this._response.StatusCode = value; + get => OriginalResponse.StatusCode; + set => OriginalResponse.StatusCode = value; } public string StatusDescription { get; set; } public string ContentType { - get => _response.ContentType; - set => _response.ContentType = value; + get => OriginalResponse.ContentType; + set => OriginalResponse.ContentType = value; } - public IHeaderDictionary Headers => _response.Headers; - public void AddHeader(string name, string value) { if (string.Equals(name, "Content-Type", StringComparison.OrdinalIgnoreCase)) @@ -56,22 +46,15 @@ namespace Emby.Server.Implementations.SocketSharp return; } - _response.Headers.Add(name, value); - } - - public string GetHeader(string name) - { - return _response.Headers[name]; + OriginalResponse.Headers.Add(name, value); } public void Redirect(string url) { - _response.Redirect(url); + OriginalResponse.Redirect(url); } - public Stream OutputStream => _response.Body; - - public bool IsClosed { get; set; } + public Stream OutputStream => OriginalResponse.Body; public bool SendChunked { get; set; } diff --git a/MediaBrowser.Model/Services/IRequest.cs b/MediaBrowser.Model/Services/IRequest.cs index edb5a2509b..4f6ddb476e 100644 --- a/MediaBrowser.Model/Services/IRequest.cs +++ b/MediaBrowser.Model/Services/IRequest.cs @@ -101,7 +101,7 @@ namespace MediaBrowser.Model.Services public interface IResponse { - IRequest Request { get; } + HttpResponse OriginalResponse { get; } int StatusCode { get; set; } @@ -111,22 +111,10 @@ namespace MediaBrowser.Model.Services void AddHeader(string name, string value); - string GetHeader(string name); - void Redirect(string url); Stream OutputStream { get; } - /// - /// Gets a value indicating whether this instance is closed. - /// - bool IsClosed { get; set; } - - //Add Metadata to Response - Dictionary Items { get; } - - IHeaderDictionary Headers { get; } - Task TransmitFile(string path, long offset, long count, FileShareMode fileShareMode, IFileSystem fileSystem, IStreamHelper streamHelper, CancellationToken cancellationToken); bool SendChunked { get; set; } From decaffed86cbc5db99c3f79d266fdfcdd262c12d Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 17:39:40 +0100 Subject: [PATCH 128/166] Remove EnvironmentInfo This moved the last bit of usefulness of EnvironmentInfo into a static class. --- Emby.Dlna/Main/DlnaEntryPoint.cs | 13 ++-- .../IsoMounter/LinuxIsoManager.cs | 10 ++- .../ApplicationHost.cs | 47 ++++-------- .../EnvironmentInfo/EnvironmentInfo.cs | 36 ---------- .../IO/LibraryMonitor.cs | 15 +--- .../IO/ManagedFileSystem.cs | 16 ++--- .../Networking/NetworkManager.cs | 8 +-- Jellyfin.Server/CoreAppHost.cs | 3 - Jellyfin.Server/Program.cs | 51 ++----------- MediaBrowser.Api/LiveTv/LiveTvService.cs | 8 +-- .../LiveTv/ProgressiveFileCopier.cs | 8 +-- .../Playback/Progressive/AudioService.cs | 8 +-- .../BaseProgressiveStreamingService.cs | 15 ++-- .../Progressive/ProgressiveStreamWriter.cs | 10 ++- .../Playback/Progressive/VideoService.cs | 8 +-- .../Playback/UniversalAudioService.cs | 7 +- MediaBrowser.Common/IApplicationHost.cs | 3 +- MediaBrowser.Common/System/OperatingSystem.cs | 72 +++++++++++++++++++ MediaBrowser.Model/System/IEnvironmentInfo.cs | 21 ------ .../System/OperatingSystemId.cs | 10 +++ 20 files changed, 144 insertions(+), 225 deletions(-) delete mode 100644 Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs create mode 100644 MediaBrowser.Common/System/OperatingSystem.cs delete mode 100644 MediaBrowser.Model/System/IEnvironmentInfo.cs create mode 100644 MediaBrowser.Model/System/OperatingSystemId.cs diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 57ed0097a0..919a8f46f8 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Emby.Dlna.PlayTo; @@ -24,6 +23,7 @@ using MediaBrowser.Model.Xml; using Microsoft.Extensions.Logging; using Rssdp; using Rssdp.Infrastructure; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Dlna.Main { @@ -48,9 +48,8 @@ namespace Emby.Dlna.Main private readonly IDeviceDiscovery _deviceDiscovery; private SsdpDevicePublisher _Publisher; - + private readonly ISocketFactory _socketFactory; - private readonly IEnvironmentInfo _environmentInfo; private readonly INetworkManager _networkManager; private ISsdpCommunicationsServer _communicationsServer; @@ -76,7 +75,6 @@ namespace Emby.Dlna.Main IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, - IEnvironmentInfo environmentInfo, INetworkManager networkManager, IUserViewManager userViewManager, IXmlReaderSettingsFactory xmlReaderSettingsFactory, @@ -96,7 +94,6 @@ namespace Emby.Dlna.Main _deviceDiscovery = deviceDiscovery; _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; - _environmentInfo = environmentInfo; _networkManager = networkManager; _logger = loggerFactory.CreateLogger("Dlna"); @@ -169,8 +166,8 @@ namespace Emby.Dlna.Main { if (_communicationsServer == null) { - var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows || - _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux; + var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows || + OperatingSystem.Id == OperatingSystemId.Linux; _communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding) { @@ -230,7 +227,7 @@ namespace Emby.Dlna.Main try { - _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion, _config.GetDlnaConfiguration().SendOnlyMatchedHost); + _Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost); _Publisher.LogFunction = LogMessage; _Publisher.SupportPnpRootDevice = false; diff --git a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs index 943caa3e62..2f0003be88 100644 --- a/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs +++ b/Emby.IsoMounting/IsoMounter/LinuxIsoManager.cs @@ -7,6 +7,7 @@ using MediaBrowser.Model.Diagnostics; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace IsoMounter { @@ -17,7 +18,6 @@ namespace IsoMounter #region Private Fields - private readonly IEnvironmentInfo EnvironmentInfo; private readonly bool ExecutablesAvailable; private readonly ILogger _logger; private readonly string MountCommand; @@ -30,10 +30,8 @@ namespace IsoMounter #region Constructor(s) - public LinuxIsoManager(ILogger logger, IEnvironmentInfo environment, IProcessFactory processFactory) + public LinuxIsoManager(ILogger logger, IProcessFactory processFactory) { - - EnvironmentInfo = environment; _logger = logger; ProcessFactory = processFactory; @@ -109,7 +107,7 @@ namespace IsoMounter public bool CanMount(string path) { - if (EnvironmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Linux) + if (OperatingSystem.Id != OperatingSystemId.Linux) { return false; } @@ -118,7 +116,7 @@ namespace IsoMounter Name, path, Path.GetExtension(path), - EnvironmentInfo.OperatingSystem, + OperatingSystem.Name, ExecutablesAvailable ); diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a581214c71..964d173421 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -115,6 +115,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using ServiceStack; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations { @@ -143,12 +144,8 @@ namespace Emby.Server.Implementations return false; } - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows) - { - return true; - } - - if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) + if (OperatingSystem.Id == OperatingSystemId.Windows + || OperatingSystem.Id == OperatingSystemId.Darwin) { return true; } @@ -218,8 +215,6 @@ namespace Emby.Server.Implementations public IFileSystem FileSystemManager { get; set; } - protected IEnvironmentInfo EnvironmentInfo { get; set; } - public PackageVersionClass SystemUpdateLevel { get @@ -239,15 +234,6 @@ namespace Emby.Server.Implementations /// The server configuration manager. public IServerConfigurationManager ServerConfigurationManager => (IServerConfigurationManager)ConfigurationManager; - /// - /// Gets the configuration manager. - /// - /// IConfigurationManager. - protected IConfigurationManager GetConfigurationManager() - { - return new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); - } - protected virtual IResourceFileManager CreateResourceFileManager() { return new ResourceFileManager(HttpResultFactory, LoggerFactory, FileSystemManager); @@ -356,7 +342,6 @@ namespace Emby.Server.Implementations ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, - IEnvironmentInfo environmentInfo, IImageEncoder imageEncoder, INetworkManager networkManager, IConfiguration configuration) @@ -370,13 +355,12 @@ namespace Emby.Server.Implementations NetworkManager = networkManager; networkManager.LocalSubnetsFn = GetConfiguredLocalSubnets; - EnvironmentInfo = environmentInfo; ApplicationPaths = applicationPaths; LoggerFactory = loggerFactory; FileSystemManager = fileSystem; - ConfigurationManager = GetConfigurationManager(); + ConfigurationManager = new ServerConfigurationManager(ApplicationPaths, LoggerFactory, XmlSerializer, FileSystemManager); Logger = LoggerFactory.CreateLogger("App"); @@ -532,7 +516,7 @@ namespace Emby.Server.Implementations /// /// Runs the startup tasks. /// - public async Task RunStartupTasks() + public async Task RunStartupTasksAsync() { Logger.LogInformation("Running startup tasks"); @@ -584,7 +568,7 @@ namespace Emby.Server.Implementations } } - public async Task Init(IServiceCollection serviceCollection) + public async Task InitAsync(IServiceCollection serviceCollection) { HttpPort = ServerConfigurationManager.Configuration.HttpServerPortNumber; HttpsPort = ServerConfigurationManager.Configuration.HttpsPortNumber; @@ -706,8 +690,6 @@ namespace Emby.Server.Implementations serviceCollection.AddLogging(); serviceCollection.AddSingleton(Logger); - serviceCollection.AddSingleton(EnvironmentInfo); - serviceCollection.AddSingleton(FileSystemManager); serviceCollection.AddSingleton(); @@ -786,7 +768,7 @@ namespace Emby.Server.Implementations var musicManager = new MusicManager(LibraryManager); serviceCollection.AddSingleton(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager, EnvironmentInfo); + LibraryMonitor = new LibraryMonitor(LoggerFactory, LibraryManager, ServerConfigurationManager, FileSystemManager); serviceCollection.AddSingleton(LibraryMonitor); serviceCollection.AddSingleton(new SearchEngine(LoggerFactory, LibraryManager, UserManager)); @@ -907,7 +889,7 @@ namespace Emby.Server.Implementations public virtual string PackageRuntime => "netcore"; - public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths, EnvironmentInfo.EnvironmentInfo environmentInfo) + public static void LogEnvironmentInfo(ILogger logger, IApplicationPaths appPaths) { // Distinct these to prevent users from reporting problems that aren't actually problems var commandLineArgs = Environment @@ -915,8 +897,9 @@ namespace Emby.Server.Implementations .Distinct(); logger.LogInformation("Arguments: {Args}", commandLineArgs); - logger.LogInformation("Operating system: {OS} {OSVersion}", environmentInfo.OperatingSystemName, environmentInfo.OperatingSystemVersion); - logger.LogInformation("Architecture: {Architecture}", environmentInfo.SystemArchitecture); + // FIXME: @bond this logs the kernel version, not the OS version + logger.LogInformation("Operating system: {OS} {OSVersion}", OperatingSystem.Name, Environment.OSVersion.Version); + logger.LogInformation("Architecture: {Architecture}", RuntimeInformation.OSArchitecture); logger.LogInformation("64-Bit Process: {Is64Bit}", Environment.Is64BitProcess); logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount); @@ -1400,8 +1383,8 @@ namespace Emby.Server.Implementations HttpServerPortNumber = HttpPort, SupportsHttps = SupportsHttps, HttpsPortNumber = HttpsPort, - OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), - OperatingSystemDisplayName = EnvironmentInfo.OperatingSystemName, + OperatingSystem = OperatingSystem.Id.ToString(), + OperatingSystemDisplayName = OperatingSystem.Name, CanSelfRestart = CanSelfRestart, CanLaunchWebBrowser = CanLaunchWebBrowser, WanAddress = wanAddress, @@ -1411,7 +1394,7 @@ namespace Emby.Server.Implementations LocalAddress = localAddress, SupportsLibraryMonitor = true, EncoderLocation = MediaEncoder.EncoderLocation, - SystemArchitecture = EnvironmentInfo.SystemArchitecture, + SystemArchitecture = RuntimeInformation.OSArchitecture, SystemUpdateLevel = SystemUpdateLevel, PackageName = StartupOptions.PackageName }; @@ -1435,7 +1418,7 @@ namespace Emby.Server.Implementations { Version = ApplicationVersion, Id = SystemId, - OperatingSystem = EnvironmentInfo.OperatingSystem.ToString(), + OperatingSystem = OperatingSystem.Id.ToString(), WanAddress = wanAddress, ServerName = FriendlyName, LocalAddress = localAddress diff --git a/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs b/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs deleted file mode 100644 index c8104150d6..0000000000 --- a/Emby.Server.Implementations/EnvironmentInfo/EnvironmentInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using MediaBrowser.Model.System; - -namespace Emby.Server.Implementations.EnvironmentInfo -{ - public class EnvironmentInfo : IEnvironmentInfo - { - public EnvironmentInfo(MediaBrowser.Model.System.OperatingSystem operatingSystem) - { - OperatingSystem = operatingSystem; - } - - public MediaBrowser.Model.System.OperatingSystem OperatingSystem { get; private set; } - - public string OperatingSystemName - { - get - { - switch (OperatingSystem) - { - case MediaBrowser.Model.System.OperatingSystem.Android: return "Android"; - case MediaBrowser.Model.System.OperatingSystem.BSD: return "BSD"; - case MediaBrowser.Model.System.OperatingSystem.Linux: return "Linux"; - case MediaBrowser.Model.System.OperatingSystem.OSX: return "macOS"; - case MediaBrowser.Model.System.OperatingSystem.Windows: return "Windows"; - default: throw new Exception($"Unknown OS {OperatingSystem}"); - } - } - } - - public string OperatingSystemVersion => Environment.OSVersion.Version.ToString() + " " + Environment.OSVersion.ServicePack.ToString(); - - public Architecture SystemArchitecture => RuntimeInformation.OSArchitecture; - } -} diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index d473425115..df4dc41b99 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -10,8 +10,8 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; -using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.IO { @@ -127,7 +127,6 @@ namespace Emby.Server.Implementations.IO private IServerConfigurationManager ConfigurationManager { get; set; } private readonly IFileSystem _fileSystem; - private readonly IEnvironmentInfo _environmentInfo; /// /// Initializes a new instance of the class. @@ -136,14 +135,12 @@ namespace Emby.Server.Implementations.IO ILoggerFactory loggerFactory, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, - IFileSystem fileSystem, - IEnvironmentInfo environmentInfo) + IFileSystem fileSystem) { LibraryManager = libraryManager; Logger = loggerFactory.CreateLogger(GetType().Name); ConfigurationManager = configurationManager; _fileSystem = fileSystem; - _environmentInfo = environmentInfo; } private bool IsLibraryMonitorEnabled(BaseItem item) @@ -267,7 +264,7 @@ namespace Emby.Server.Implementations.IO return; } - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != OperatingSystemId.Windows) { if (path.StartsWith("\\\\", StringComparison.OrdinalIgnoreCase) || path.StartsWith("smb://", StringComparison.OrdinalIgnoreCase)) { @@ -276,12 +273,6 @@ namespace Emby.Server.Implementations.IO } } - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Android) - { - // causing crashing - return; - } - // Already being watched if (_fileSystemWatchers.ContainsKey(path)) { diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 421592fada..47cea7269d 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -8,6 +8,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.IO { @@ -24,22 +25,19 @@ namespace Emby.Server.Implementations.IO private readonly string _tempPath; - private readonly IEnvironmentInfo _environmentInfo; private readonly bool _isEnvironmentCaseInsensitive; public ManagedFileSystem( ILoggerFactory loggerFactory, - IEnvironmentInfo environmentInfo, IApplicationPaths applicationPaths) { Logger = loggerFactory.CreateLogger("FileSystem"); _supportsAsyncFileStreams = true; _tempPath = applicationPaths.TempDirectory; - _environmentInfo = environmentInfo; - SetInvalidFileNameChars(environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows); + SetInvalidFileNameChars(OperatingSystem.Id == OperatingSystemId.Windows); - _isEnvironmentCaseInsensitive = environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows; + _isEnvironmentCaseInsensitive = OperatingSystem.Id == OperatingSystemId.Windows; } public virtual void AddShortcutHandler(IShortcutHandler handler) @@ -468,7 +466,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetHidden(string path, bool isHidden) { - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows) { return; } @@ -492,7 +490,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetReadOnly(string path, bool isReadOnly) { - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows) { return; } @@ -516,7 +514,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetAttributes(string path, bool isHidden, bool isReadOnly) { - if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows) + if (OperatingSystem.Id != MediaBrowser.Model.System.OperatingSystemId.Windows) { return; } @@ -801,7 +799,7 @@ namespace Emby.Server.Implementations.IO public virtual void SetExecutable(string path) { - if (_environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX) + if (OperatingSystem.Id == MediaBrowser.Model.System.OperatingSystemId.Darwin) { RunProcess("chmod", "+x \"" + path + "\"", Path.GetDirectoryName(path)); } diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs index ace93ebdee..c102f9eb55 100644 --- a/Emby.Server.Implementations/Networking/NetworkManager.cs +++ b/Emby.Server.Implementations/Networking/NetworkManager.cs @@ -7,11 +7,11 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading.Tasks; using MediaBrowser.Common.Net; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations.Networking { @@ -22,14 +22,12 @@ namespace Emby.Server.Implementations.Networking public event EventHandler NetworkChanged; public Func LocalSubnetsFn { get; set; } - public NetworkManager( - ILoggerFactory loggerFactory, - IEnvironmentInfo environment) + public NetworkManager(ILoggerFactory loggerFactory) { Logger = loggerFactory.CreateLogger(nameof(NetworkManager)); // In FreeBSD these events cause a crash - if (environment.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.BSD) + if (OperatingSystem.Id != OperatingSystemId.BSD) { try { diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs index 17259c7370..8e6ed7a7e5 100644 --- a/Jellyfin.Server/CoreAppHost.cs +++ b/Jellyfin.Server/CoreAppHost.cs @@ -3,7 +3,6 @@ using System.Reflection; using Emby.Server.Implementations; using Emby.Server.Implementations.HttpServer; using MediaBrowser.Model.IO; -using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -16,7 +15,6 @@ namespace Jellyfin.Server ILoggerFactory loggerFactory, StartupOptions options, IFileSystem fileSystem, - IEnvironmentInfo environmentInfo, MediaBrowser.Controller.Drawing.IImageEncoder imageEncoder, MediaBrowser.Common.Net.INetworkManager networkManager, IConfiguration configuration) @@ -25,7 +23,6 @@ namespace Jellyfin.Server loggerFactory, options, fileSystem, - environmentInfo, imageEncoder, networkManager, configuration) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0ef1711d4c..a60f2a4f61 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using CommandLine; using Emby.Drawing; using Emby.Server.Implementations; -using Emby.Server.Implementations.EnvironmentInfo; using Emby.Server.Implementations.IO; using Emby.Server.Implementations.Networking; using Jellyfin.Drawing.Skia; @@ -46,7 +45,6 @@ namespace Jellyfin.Server const string pattern = @"^(-[^-\s]{2})"; // Match -xx, not -x, not --xx, not xx const string substitution = @"-$1"; // Prepend with additional single-hyphen var regex = new Regex(pattern); - for (var i = 0; i < args.Length; i++) { args[i] = regex.Replace(args[i], substitution); @@ -54,9 +52,7 @@ namespace Jellyfin.Server // Parse the command line arguments and either start the app or exit indicating error await Parser.Default.ParseArguments(args) - .MapResult( - options => StartApp(options), - errs => Task.FromResult(0)).ConfigureAwait(false); + .MapResult(StartApp, _ => Task.CompletedTask).ConfigureAwait(false); } public static void Shutdown() @@ -119,31 +115,29 @@ namespace Jellyfin.Server _logger.LogInformation("Jellyfin version: {Version}", Assembly.GetEntryAssembly().GetName().Version); - EnvironmentInfo environmentInfo = new EnvironmentInfo(GetOperatingSystem()); - ApplicationHost.LogEnvironmentInfo(_logger, appPaths, environmentInfo); + ApplicationHost.LogEnvironmentInfo(_logger, appPaths); SQLitePCL.Batteries_V2.Init(); // Allow all https requests ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; }); - var fileSystem = new ManagedFileSystem(_loggerFactory, environmentInfo, appPaths); + var fileSystem = new ManagedFileSystem(_loggerFactory, appPaths); using (var appHost = new CoreAppHost( appPaths, _loggerFactory, options, fileSystem, - environmentInfo, new NullImageEncoder(), - new NetworkManager(_loggerFactory, environmentInfo), + new NetworkManager(_loggerFactory), appConfig)) { - await appHost.Init(new ServiceCollection()).ConfigureAwait(false); + await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); appHost.ImageProcessor.ImageEncoder = GetImageEncoder(fileSystem, appPaths, appHost.LocalizationManager); - await appHost.RunStartupTasks().ConfigureAwait(false); + await appHost.RunStartupTasksAsync().ConfigureAwait(false); try { @@ -179,7 +173,6 @@ namespace Jellyfin.Server // ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin // ELSE use $HOME/.local/share/jellyfin var dataDir = options.DataDir; - if (string.IsNullOrEmpty(dataDir)) { dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); @@ -236,7 +229,6 @@ namespace Jellyfin.Server // ELSE IF XDG_CACHE_HOME, use $XDG_CACHE_HOME/jellyfin // ELSE HOME/.cache/jellyfin var cacheDir = options.CacheDir; - if (string.IsNullOrEmpty(cacheDir)) { cacheDir = Environment.GetEnvironmentVariable("JELLYFIN_CACHE_DIR"); @@ -270,7 +262,6 @@ namespace Jellyfin.Server // ELSE IF --datadir, use /log (assume portable run) // ELSE /log var logDir = options.LogDir; - if (string.IsNullOrEmpty(logDir)) { logDir = Environment.GetEnvironmentVariable("JELLYFIN_LOG_DIR"); @@ -364,36 +355,6 @@ namespace Jellyfin.Server return new NullImageEncoder(); } - private static MediaBrowser.Model.System.OperatingSystem GetOperatingSystem() - { - switch (Environment.OSVersion.Platform) - { - case PlatformID.MacOSX: - return MediaBrowser.Model.System.OperatingSystem.OSX; - case PlatformID.Win32NT: - return MediaBrowser.Model.System.OperatingSystem.Windows; - case PlatformID.Unix: - default: - { - string osDescription = RuntimeInformation.OSDescription; - if (osDescription.Contains("linux", StringComparison.OrdinalIgnoreCase)) - { - return MediaBrowser.Model.System.OperatingSystem.Linux; - } - else if (osDescription.Contains("darwin", StringComparison.OrdinalIgnoreCase)) - { - return MediaBrowser.Model.System.OperatingSystem.OSX; - } - else if (osDescription.Contains("bsd", StringComparison.OrdinalIgnoreCase)) - { - return MediaBrowser.Model.System.OperatingSystem.BSD; - } - - throw new Exception($"Can't resolve OS with description: '{osDescription}'"); - } - } - } - private static void StartNewInstance(StartupOptions options) { _logger.LogInformation("Starting new instance"); diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 88ed734569..486d5e8a71 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -698,12 +698,11 @@ namespace MediaBrowser.Api.LiveTv private readonly IFileSystem _fileSystem; private readonly IAuthorizationContext _authContext; private readonly ISessionContext _sessionContext; - private readonly IEnvironmentInfo _environment; private ICryptoProvider _cryptographyProvider; private IStreamHelper _streamHelper; private IMediaSourceManager _mediaSourceManager; - public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext, IEnvironmentInfo environment) + public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext) { _liveTvManager = liveTvManager; _userManager = userManager; @@ -714,7 +713,6 @@ namespace MediaBrowser.Api.LiveTv _fileSystem = fileSystem; _authContext = authContext; _sessionContext = sessionContext; - _environment = environment; _cryptographyProvider = crypto; _streamHelper = streamHelper; _mediaSourceManager = mediaSourceManager; @@ -756,7 +754,7 @@ namespace MediaBrowser.Api.LiveTv [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) }; - return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger, _environment) + return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger) { AllowEndOfFile = false }; @@ -779,7 +777,7 @@ namespace MediaBrowser.Api.LiveTv [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file." + request.Container) }; - return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger, _environment) + return new ProgressiveFileCopier(directStreamProvider, _streamHelper, outputHeaders, Logger) { AllowEndOfFile = false }; diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index 8412bf66b9..51552d928b 100644 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.LiveTv @@ -23,25 +22,22 @@ namespace MediaBrowser.Api.LiveTv public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; - private readonly IEnvironmentInfo _environment; private IStreamHelper _streamHelper; - public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) + public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _logger = logger; - _environment = environment; _streamHelper = streamHelper; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary outputHeaders, ILogger logger, IEnvironmentInfo environment) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, IStreamHelper streamHelper, Dictionary outputHeaders, ILogger logger) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _logger = logger; - _environment = environment; _streamHelper = streamHelper; } diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 48b4e2f24e..dfe4b2b8e9 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; namespace MediaBrowser.Api.Playback.Progressive { @@ -46,8 +44,7 @@ namespace MediaBrowser.Api.Playback.Progressive IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - IEnvironmentInfo environmentInfo) + IAuthorizationContext authorizationContext) : base(httpClient, serverConfig, userManager, @@ -60,8 +57,7 @@ namespace MediaBrowser.Api.Playback.Progressive deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext, - environmentInfo) + authorizationContext) { } diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index f43f26b2f3..a2c20e38fd 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -8,15 +7,12 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.Playback.Progressive @@ -26,7 +22,6 @@ namespace MediaBrowser.Api.Playback.Progressive /// public abstract class BaseProgressiveStreamingService : BaseStreamingService { - protected readonly IEnvironmentInfo EnvironmentInfo; protected IHttpClient HttpClient { get; private set; } public BaseProgressiveStreamingService( @@ -42,8 +37,7 @@ namespace MediaBrowser.Api.Playback.Progressive IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - IEnvironmentInfo environmentInfo) + IAuthorizationContext authorizationContext) : base(serverConfig, userManager, libraryManager, @@ -57,7 +51,6 @@ namespace MediaBrowser.Api.Playback.Progressive jsonSerializer, authorizationContext) { - EnvironmentInfo = environmentInfo; HttpClient = httpClient; } @@ -157,7 +150,7 @@ namespace MediaBrowser.Api.Playback.Progressive // TODO: Don't hardcode this outputHeaders[HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType("file.ts"); - return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) + return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) { AllowEndOfFile = false }; @@ -203,7 +196,7 @@ namespace MediaBrowser.Api.Playback.Progressive }; - return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, EnvironmentInfo, CancellationToken.None) + return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None) { AllowEndOfFile = false }; @@ -397,7 +390,7 @@ namespace MediaBrowser.Api.Playback.Progressive outputHeaders[item.Key] = item.Value; } - return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, EnvironmentInfo, CancellationToken.None); + return new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None); } finally { diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 3dd9de2a1b..6609120655 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -8,6 +8,7 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Services; using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; +using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace MediaBrowser.Api.Playback.Progressive { @@ -27,9 +28,8 @@ namespace MediaBrowser.Api.Playback.Progressive public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; - private readonly IEnvironmentInfo _environment; - public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; _path = path; @@ -37,17 +37,15 @@ namespace MediaBrowser.Api.Playback.Progressive _job = job; _logger = logger; _cancellationToken = cancellationToken; - _environment = environment; } - public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, TranscodingJob job, ILogger logger, IEnvironmentInfo environment, CancellationToken cancellationToken) + public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _job = job; _logger = logger; _cancellationToken = cancellationToken; - _environment = environment; } public IDictionary Headers => _outputHeaders; @@ -79,7 +77,7 @@ namespace MediaBrowser.Api.Playback.Progressive var eofCount = 0; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = _environment.OperatingSystem != Model.System.OperatingSystem.Windows; + var allowAsyncFileRead = OperatingSystem.Id != OperatingSystemId.Windows; using (var inputStream = GetInputStream(allowAsyncFileRead)) { diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index bf15cc756c..ab19fdc261 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -3,7 +3,6 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; -using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; @@ -11,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; namespace MediaBrowser.Api.Playback.Progressive { @@ -83,8 +81,7 @@ namespace MediaBrowser.Api.Playback.Progressive IDeviceManager deviceManager, IMediaSourceManager mediaSourceManager, IJsonSerializer jsonSerializer, - IAuthorizationContext authorizationContext, - IEnvironmentInfo environmentInfo) + IAuthorizationContext authorizationContext) : base(httpClient, serverConfig, userManager, @@ -97,8 +94,7 @@ namespace MediaBrowser.Api.Playback.Progressive deviceManager, mediaSourceManager, jsonSerializer, - authorizationContext, - environmentInfo) + authorizationContext) { } diff --git a/MediaBrowser.Api/Playback/UniversalAudioService.cs b/MediaBrowser.Api/Playback/UniversalAudioService.cs index f97e88e986..b3d8bfe59f 100644 --- a/MediaBrowser.Api/Playback/UniversalAudioService.cs +++ b/MediaBrowser.Api/Playback/UniversalAudioService.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Extensions.Logging; namespace MediaBrowser.Api.Playback @@ -93,7 +92,6 @@ namespace MediaBrowser.Api.Playback IAuthorizationContext authorizationContext, IImageProcessor imageProcessor, INetworkManager networkManager, - IEnvironmentInfo environmentInfo, ILoggerFactory loggerFactory) { HttpClient = httpClient; @@ -112,7 +110,6 @@ namespace MediaBrowser.Api.Playback AuthorizationContext = authorizationContext; ImageProcessor = imageProcessor; NetworkManager = networkManager; - EnvironmentInfo = environmentInfo; _loggerFactory = loggerFactory; _logger = loggerFactory.CreateLogger(nameof(UniversalAudioService)); } @@ -133,7 +130,6 @@ namespace MediaBrowser.Api.Playback protected IAuthorizationContext AuthorizationContext { get; private set; } protected IImageProcessor ImageProcessor { get; private set; } protected INetworkManager NetworkManager { get; private set; } - protected IEnvironmentInfo EnvironmentInfo { get; private set; } private ILoggerFactory _loggerFactory; private ILogger _logger; @@ -338,8 +334,7 @@ namespace MediaBrowser.Api.Playback DeviceManager, MediaSourceManager, JsonSerializer, - AuthorizationContext, - EnvironmentInfo) + AuthorizationContext) { Request = Request }; diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 3a4098612d..966448d630 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Plugins; using MediaBrowser.Model.Events; @@ -107,7 +106,7 @@ namespace MediaBrowser.Common /// /// Inits this instance. /// - Task Init(IServiceCollection serviceCollection); + Task InitAsync(IServiceCollection serviceCollection); /// /// Creates the instance. diff --git a/MediaBrowser.Common/System/OperatingSystem.cs b/MediaBrowser.Common/System/OperatingSystem.cs new file mode 100644 index 0000000000..640821d4d7 --- /dev/null +++ b/MediaBrowser.Common/System/OperatingSystem.cs @@ -0,0 +1,72 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using MediaBrowser.Model.System; + +namespace MediaBrowser.Common.System +{ + public static class OperatingSystem + { + // We can't use Interlocked.CompareExchange for enums + private static int _id = int.MaxValue; + + public static OperatingSystemId Id + { + get + { + if (_id == int.MaxValue) + { + Interlocked.CompareExchange(ref _id, (int)GetId(), int.MaxValue); + } + + return (OperatingSystemId)_id; + } + } + + public static string Name + { + get + { + switch (Id) + { + case OperatingSystemId.BSD: return "BSD"; + case OperatingSystemId.Linux: return "Linux"; + case OperatingSystemId.Darwin: return "macOS"; + case OperatingSystemId.Windows: return "Windows"; + default: throw new Exception($"Unknown OS {Id}"); + } + } + } + + private static OperatingSystemId GetId() + { + switch (Environment.OSVersion.Platform) + { + // On .NET Core `MacOSX` got replaced by `Unix`, this case should never be hit. + case PlatformID.MacOSX: + return OperatingSystemId.Darwin; + case PlatformID.Win32NT: + return OperatingSystemId.Windows; + case PlatformID.Unix: + default: + { + string osDescription = RuntimeInformation.OSDescription; + if (osDescription.IndexOf("linux", StringComparison.OrdinalIgnoreCase) != -1) + { + return OperatingSystemId.Linux; + } + else if (osDescription.IndexOf("darwin", StringComparison.OrdinalIgnoreCase) != -1) + { + return OperatingSystemId.Darwin; + } + else if (osDescription.IndexOf("bsd", StringComparison.OrdinalIgnoreCase) != -1) + { + return OperatingSystemId.BSD; + } + + throw new Exception($"Can't resolve OS with description: '{osDescription}'"); + } + } + } + } +} diff --git a/MediaBrowser.Model/System/IEnvironmentInfo.cs b/MediaBrowser.Model/System/IEnvironmentInfo.cs deleted file mode 100644 index 3ffcc7de14..0000000000 --- a/MediaBrowser.Model/System/IEnvironmentInfo.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.InteropServices; - -namespace MediaBrowser.Model.System -{ - public interface IEnvironmentInfo - { - OperatingSystem OperatingSystem { get; } - string OperatingSystemName { get; } - string OperatingSystemVersion { get; } - Architecture SystemArchitecture { get; } - } - - public enum OperatingSystem - { - Windows, - Linux, - OSX, - BSD, - Android - } -} diff --git a/MediaBrowser.Model/System/OperatingSystemId.cs b/MediaBrowser.Model/System/OperatingSystemId.cs new file mode 100644 index 0000000000..e81dd4213f --- /dev/null +++ b/MediaBrowser.Model/System/OperatingSystemId.cs @@ -0,0 +1,10 @@ +namespace MediaBrowser.Model.System +{ + public enum OperatingSystemId + { + Windows, + Linux, + Darwin, + BSD + } +} From e3b844b5aa34ef6a8f4a23c053115f4eabadcbf7 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Thu, 7 Mar 2019 22:49:41 +0100 Subject: [PATCH 129/166] Add urlprefixes during init --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Emby.Server.Implementations/HttpServer/HttpListenerHost.cs | 6 ++++-- MediaBrowser.Controller/Net/IHttpServer.cs | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index a581214c71..f7d9bad1b0 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1056,7 +1056,7 @@ namespace Emby.Server.Implementations .Where(i => i != null) .ToArray(); - HttpServer.Init(GetExports(false), GetExports()); + HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); LibraryManager.AddParts(GetExports(), GetExports(), diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index eab755cefb..e8d47cad52 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -676,10 +676,12 @@ namespace Emby.Server.Implementations.HttpServer /// Adds the rest handlers. /// /// The services. - public void Init(IEnumerable services, IEnumerable listeners) + /// + /// + public void Init(IEnumerable services, IEnumerable listeners, IEnumerable urlPrefixes) { _webSocketListeners = listeners.ToArray(); - + UrlPrefixes = urlPrefixes.ToArray(); ServiceController = new ServiceController(); Logger.LogInformation("Calling ServiceStack AppHost.Init"); diff --git a/MediaBrowser.Controller/Net/IHttpServer.cs b/MediaBrowser.Controller/Net/IHttpServer.cs index cede9a98fb..46933c0465 100644 --- a/MediaBrowser.Controller/Net/IHttpServer.cs +++ b/MediaBrowser.Controller/Net/IHttpServer.cs @@ -32,7 +32,7 @@ namespace MediaBrowser.Controller.Net /// /// Inits this instance. /// - void Init(IEnumerable services, IEnumerable listener); + void Init(IEnumerable services, IEnumerable listener, IEnumerable urlPrefixes); /// /// If set, all requests will respond with this message From 4625592a8377b1eb04974f39bc220a362e960ca4 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Thu, 7 Mar 2019 22:52:41 +0100 Subject: [PATCH 130/166] Quick nullref fix --- MediaBrowser.Controller/Entities/BaseItem.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 39b6eedfeb..154b9470ef 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -36,6 +36,12 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BaseItem : IHasProviderIds, IHasLookupInfo { + /// + /// The supported image extensions + /// + public static readonly IReadOnlyList SupportedImageExtensions + = new [] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; + private static readonly List _supportedExtensions = new List(SupportedImageExtensions) { ".nfo", @@ -69,11 +75,6 @@ namespace MediaBrowser.Controller.Entities public static readonly char[] SlugReplaceChars = { '?', '/', '&' }; public static char SlugChar = '-'; - /// - /// The supported image extensions - /// - public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; - /// /// The trailer folder name /// From 80fb3dc2cb720269050a7a2d952bbb269c9e88c8 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 06:36:00 +0100 Subject: [PATCH 131/166] Fix error --- MediaBrowser.Controller/Entities/BaseItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 154b9470ef..e20641c99a 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.Controller.Entities /// /// The supported image extensions /// - public static readonly IReadOnlyList SupportedImageExtensions + public static readonly string[] SupportedImageExtensions = new [] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; private static readonly List _supportedExtensions = new List(SupportedImageExtensions) From 045a0419f6983acfffe53591d24386c6c59d3901 Mon Sep 17 00:00:00 2001 From: Anthony Lavado Date: Fri, 8 Mar 2019 02:33:08 -0500 Subject: [PATCH 132/166] remove new file header text, and remove it from solution This removes the new file header text file from the repository, and also removes the include that was in the project solution. --- MediaBrowser.sln | 2 +- new-file-header.txt | 22 ---------------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 new-file-header.txt diff --git a/MediaBrowser.sln b/MediaBrowser.sln index e43a582242..a3c0569bad 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -180,7 +180,7 @@ Global GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 $0.StandardHeader = $1 - $1.Text = @### This header should be used to start new files.\n### It provides an explicit per-file license reference that should be present on all new files.\n### To use this header, delete these lines and the following empty line, modify to\n### the proper full path, and then add new code following the header and a single empty line.\n\n// ${FileName}\n// Part of the Jellyfin project (https://jellyfin.media)\n//\n// All copyright belongs to the Jellyfin contributors; a full list can\n// be found in the file CONTRIBUTORS.md\n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, version 2.\n//\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with this program. If not, see .\n + $1.IncludeInNewFiles = False $0.DotNetNamingPolicy = $2 $2.DirectoryNamespaceAssociation = PrefixedHierarchical EndGlobalSection diff --git a/new-file-header.txt b/new-file-header.txt deleted file mode 100644 index 4247a8e246..0000000000 --- a/new-file-header.txt +++ /dev/null @@ -1,22 +0,0 @@ -### This header should be used to start new files. -### It provides an explicit per-file license reference that should be present on all new files. -### To use this header, delete these lines and the following empty line, modify to -### the proper full path, and then add new code following the header and a single empty line. - -// -// Part of the Jellyfin project (https://jellyfin.media) -// -// All copyright belongs to the Jellyfin contributors; a full list can -// be found in the file CONTRIBUTORS.md -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 2. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . From e498e471091c8dd6f03b71c9f269dd546bc8dde3 Mon Sep 17 00:00:00 2001 From: dkanada Date: Fri, 8 Mar 2019 18:39:41 +0900 Subject: [PATCH 133/166] remove mirror images from library thumbnail --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 52 ++------------------ 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index dfdf398710..f2162432a9 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -78,20 +78,15 @@ namespace Jellyfin.Drawing.Skia canvas.Clear(SKColors.Black); // determine sizes for each image that will composited into the final image - var iSlice = Convert.ToInt32(width * 0.23475); - int iTrans = Convert.ToInt32(height * .25); - int iHeight = Convert.ToInt32(height * .70); - var horizontalImagePadding = Convert.ToInt32(width * 0.0125); - var verticalSpacing = Convert.ToInt32(height * 0.01111111111111111111111111111111); + var iSlice = Convert.ToInt32(width * 0.33); + int iTrans = Convert.ToInt32(height * 0.25); + int iHeight = Convert.ToInt32(height * 1.00); int imageIndex = 0; - - for (int i = 0; i < 4; i++) + for (int i = 0; i < 3; i++) { - using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) { imageIndex = newIndex; - if (currentBitmap == null) { continue; @@ -108,44 +103,7 @@ namespace Jellyfin.Drawing.Skia using (var subset = image.Subset(SKRectI.Create(ix, 0, iSlice, iHeight))) { // draw image onto canvas - canvas.DrawImage(subset ?? image, (horizontalImagePadding * (i + 1)) + (iSlice * i), verticalSpacing); - - if (subset == null) - { - continue; - } - // create reflection of image below the drawn image - using (var croppedBitmap = SKBitmap.FromImage(subset)) - using (var reflectionBitmap = new SKBitmap(croppedBitmap.Width, croppedBitmap.Height / 2, croppedBitmap.ColorType, croppedBitmap.AlphaType)) - { - // resize to half height - currentBitmap.ScalePixels(reflectionBitmap, SKFilterQuality.High); - - using (var flippedBitmap = new SKBitmap(reflectionBitmap.Width, reflectionBitmap.Height, reflectionBitmap.ColorType, reflectionBitmap.AlphaType)) - using (var flippedCanvas = new SKCanvas(flippedBitmap)) - { - // flip image vertically - var matrix = SKMatrix.MakeScale(1, -1); - matrix.SetScaleTranslate(1, -1, 0, flippedBitmap.Height); - flippedCanvas.SetMatrix(matrix); - flippedCanvas.DrawBitmap(reflectionBitmap, 0, 0); - flippedCanvas.ResetMatrix(); - - // create gradient to make image appear as a reflection - var remainingHeight = height - (iHeight + (2 * verticalSpacing)); - flippedCanvas.ClipRect(SKRect.Create(reflectionBitmap.Width, remainingHeight)); - using (var gradient = new SKPaint()) - { - gradient.IsAntialias = true; - gradient.BlendMode = SKBlendMode.SrcOver; - gradient.Shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(0, remainingHeight), new[] { new SKColor(0, 0, 0, 128), new SKColor(0, 0, 0, 208), new SKColor(0, 0, 0, 240), new SKColor(0, 0, 0, 255) }, null, SKShaderTileMode.Clamp); - flippedCanvas.DrawPaint(gradient); - } - - // finally draw reflection onto canvas - canvas.DrawBitmap(flippedBitmap, (horizontalImagePadding * (i + 1)) + (iSlice * i), iHeight + (2 * verticalSpacing)); - } - } + canvas.DrawImage(subset ?? image, iSlice * i, 0); } } } From 58061a729528e3969b07b35fdaac534f5948eb89 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Fri, 8 Mar 2019 17:15:52 +0100 Subject: [PATCH 134/166] Make MusicBrainz base url configurable --- Emby.Server.Implementations/ConfigurationOptions.cs | 3 ++- MediaBrowser.Providers/MediaBrowser.Providers.csproj | 1 + .../Music/MusicBrainzAlbumProvider.cs | 12 ++++++++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/ConfigurationOptions.cs b/Emby.Server.Implementations/ConfigurationOptions.cs index 30bfd87498..9bc60972a1 100644 --- a/Emby.Server.Implementations/ConfigurationOptions.cs +++ b/Emby.Server.Implementations/ConfigurationOptions.cs @@ -6,7 +6,8 @@ namespace Emby.Server.Implementations { public static readonly Dictionary Configuration = new Dictionary { - {"HttpListenerHost:DefaultRedirectPath", "web/index.html"} + {"HttpListenerHost:DefaultRedirectPath", "web/index.html"}, + {"MusicBrainz:BaseUrl", "https://www.musicbrainz.org"} }; } } diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 52a52efdc0..cfbb85ea6b 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -11,6 +11,7 @@ + diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index e4bb52217c..7e47c0b2a5 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -16,6 +16,7 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Xml; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Music @@ -30,15 +31,22 @@ namespace MediaBrowser.Providers.Music private readonly IJsonSerializer _json; private readonly IXmlReaderSettingsFactory _xmlSettings; - public static string MusicBrainzBaseUrl = "https://www.musicbrainz.org"; + public readonly string MusicBrainzBaseUrl; - public MusicBrainzAlbumProvider(IHttpClient httpClient, IApplicationHost appHost, ILogger logger, IJsonSerializer json, IXmlReaderSettingsFactory xmlSettings) + public MusicBrainzAlbumProvider( + IHttpClient httpClient, + IApplicationHost appHost, + ILogger logger, + IJsonSerializer json, + IXmlReaderSettingsFactory xmlSettings, + IConfiguration configuration) { _httpClient = httpClient; _appHost = appHost; _logger = logger; _json = json; _xmlSettings = xmlSettings; + MusicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; Current = this; } From f2062ba19bb38a2dfa6b10ab778718730fe0f14a Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Fri, 8 Mar 2019 22:25:45 +0100 Subject: [PATCH 135/166] Disable HTTPS in Kestrel if Certificate is null --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f7d9bad1b0..ae54606747 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -629,7 +629,7 @@ namespace Emby.Server.Implementations { options.ListenAnyIP(HttpPort); - if (EnableHttps) + if (EnableHttps && Certificate != null) { options.ListenAnyIP(HttpsPort, listenOptions => { listenOptions.UseHttps(Certificate); }); } From 47095e6cf87d73aad2abf2f9f326631cf95704fa Mon Sep 17 00:00:00 2001 From: dkanada Date: Sat, 9 Mar 2019 11:17:02 +0900 Subject: [PATCH 136/166] move a variable out of for loop --- Jellyfin.Drawing.Skia/StripCollageBuilder.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs index f2162432a9..7d404ce644 100644 --- a/Jellyfin.Drawing.Skia/StripCollageBuilder.cs +++ b/Jellyfin.Drawing.Skia/StripCollageBuilder.cs @@ -77,12 +77,14 @@ namespace Jellyfin.Drawing.Skia { canvas.Clear(SKColors.Black); + // number of images used in the thumbnail + var iCount = 3; + // determine sizes for each image that will composited into the final image - var iSlice = Convert.ToInt32(width * 0.33); - int iTrans = Convert.ToInt32(height * 0.25); + var iSlice = Convert.ToInt32(width / iCount); int iHeight = Convert.ToInt32(height * 1.00); int imageIndex = 0; - for (int i = 0; i < 3; i++) + for (int i = 0; i < iCount; i++) { using (var currentBitmap = GetNextValidImage(paths, imageIndex, out int newIndex)) { From 93fe7957fb4ed4f85b14bc4560f8e595540e91bd Mon Sep 17 00:00:00 2001 From: Matsuri Date: Fri, 1 Mar 2019 05:50:39 +0000 Subject: [PATCH 137/166] Translated using Weblate (Chinese (Simplified)) Currently translated at 97.8% (92 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/ --- Emby.Server.Implementations/Localization/Core/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json index 8910a6bce8..6f7d362d3b 100644 --- a/Emby.Server.Implementations/Localization/Core/zh-CN.json +++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json @@ -34,14 +34,14 @@ "LabelRunningTimeValue": "运行时间:{0}", "Latest": "最新", "MessageApplicationUpdated": "Jellyfin 服务器已更新", - "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}", + "MessageApplicationUpdatedTo": "Jellyfin Server 的版本已更新为 {0}", "MessageNamedServerConfigurationUpdatedWithValue": "服务器配置 {0} 部分已更新", "MessageServerConfigurationUpdated": "服务器配置已更新", "MixedContent": "混合内容", "Movies": "电影", "Music": "音乐", "MusicVideos": "音乐视频", - "NameInstallFailed": "{0} installation failed", + "NameInstallFailed": "{0} 安装失败", "NameSeasonNumber": "季 {0}", "NameSeasonUnknown": "未知季", "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.", @@ -70,7 +70,7 @@ "ProviderValue": "提供商:{0}", "ScheduledTaskFailedWithName": "{0} 已失败", "ScheduledTaskStartedWithName": "{0} 已开始", - "ServerNameNeedsToBeRestarted": "{0} needs to be restarted", + "ServerNameNeedsToBeRestarted": "{0} 需要重新启动", "Shows": "节目", "Songs": "歌曲", "StartupEmbyServerIsLoading": "Jellyfin 服务器加载中。请稍后再试。", From 16adaa64c9c94ea35ffd99df114a0b8de9249cf6 Mon Sep 17 00:00:00 2001 From: WWWesten Date: Mon, 4 Mar 2019 12:00:32 +0000 Subject: [PATCH 138/166] Translated using Weblate (Kazakh) Currently translated at 100.0% (94 of 94 strings) Translation: Jellyfin/Jellyfin Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/kk/ --- Emby.Server.Implementations/Localization/Core/kk.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Emby.Server.Implementations/Localization/Core/kk.json b/Emby.Server.Implementations/Localization/Core/kk.json index 658d168e9f..23841f37d6 100644 --- a/Emby.Server.Implementations/Localization/Core/kk.json +++ b/Emby.Server.Implementations/Localization/Core/kk.json @@ -3,15 +3,15 @@ "AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}", "Application": "Qoldanba", "Artists": "Oryndaýshylar", - "AuthenticationSucceededWithUserName": "{0} túpnusqalyǵyn rastalýy sátti", + "AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy", "Books": "Kitaptar", - "CameraImageUploadedFrom": "Jańa sýret {0} kamerasynan júktep alyndy", + "CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep alyndy", "Channels": "Arnalar", "ChapterNameValue": "{0}-sahna", "Collections": "Jıyntyqtar", "DeviceOfflineWithName": "{0} ajyratylǵan", "DeviceOnlineWithName": "{0} qosylǵan", - "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz", + "FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz aıaqtaldy", "Favorites": "Tańdaýlylar", "Folders": "Qaltalar", "Genres": "Janrlar", @@ -28,13 +28,13 @@ "HeaderRecordingGroups": "Jazba toptary", "HomeVideos": "Úılik beıneler", "Inherit": "Muraǵa ıelený", - "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústelindi", + "ItemAddedWithName": "{0} tasyǵyshhanaǵa ústeldi", "ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy", "LabelIpAddressValue": "IP-mekenjaıy: {0}", "LabelRunningTimeValue": "Oınatý ýaqyty: {0}", "Latest": "Eń keıingi", "MessageApplicationUpdated": "Jellyfin Serveri jańartyldy", - "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} deńgeıge jańartyldy", + "MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy", "MessageNamedServerConfigurationUpdatedWithValue": "Server teńsheliminiń {0} bólimi jańartyldy", "MessageServerConfigurationUpdated": "Server teńshelimi jańartyldy", "MixedContent": "Aralas mazmun", From 93d15cd96972588bbbc29a9398c9d4b050d30411 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 16:17:48 -0400 Subject: [PATCH 139/166] Add configuration flag for Web directory --- .../AppBase/BaseApplicationPaths.cs | 4 +++- .../ServerApplicationPaths.cs | 6 ++++-- Jellyfin.Server/Program.cs | 19 ++++++++++++++++++- Jellyfin.Server/StartupOptions.cs | 3 +++ 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 65cdccfa5d..3ee09c9c04 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -17,12 +17,14 @@ namespace Emby.Server.Implementations.AppBase string programDataPath, string logDirectoryPath, string configurationDirectoryPath, - string cacheDirectoryPath) + string cacheDirectoryPath, + string webDirectoryPath) { ProgramDataPath = programDataPath; LogDirectoryPath = logDirectoryPath; ConfigurationDirectoryPath = configurationDirectoryPath; CachePath = cacheDirectoryPath; + WebPath = webDirectoryPath; DataPath = Path.Combine(ProgramDataPath, "data"); } diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 05f6469ece..adaf23234f 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -17,11 +17,13 @@ namespace Emby.Server.Implementations string programDataPath, string logDirectoryPath, string configurationDirectoryPath, - string cacheDirectoryPath) + string cacheDirectoryPath, + string webDirectoryPath) : base(programDataPath, logDirectoryPath, configurationDirectoryPath, - cacheDirectoryPath) + cacheDirectoryPath, + webDirectoryPath) { } diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0ef1711d4c..3d81ade35c 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -264,6 +264,23 @@ namespace Jellyfin.Server } } + // webDir + // IF --webdir + // ELSE IF $JELLYFIN_WEB_DIR + // ELSE use /jellyfin-web + var webDir = options.WebDir; + + if (string.IsNullOrEmpty(webDir)) + { + webDir = Environment.GetEnvironmentVariable("JELLYFIN_WEB_DIR"); + + if (string.IsNullOrEmpty(webDir)) + { + // Use default location under ResourcesPath + webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web") + } + } + // logDir // IF --logdir // ELSE IF $JELLYFIN_LOG_DIR @@ -296,7 +313,7 @@ namespace Jellyfin.Server Environment.Exit(1); } - return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir); + return new ServerApplicationPaths(dataDir, logDir, configDir, cacheDir, webDir); } private static async Task CreateConfiguration(IApplicationPaths appPaths) diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index c8cdb984db..2b01c7bbdf 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -11,6 +11,9 @@ namespace Jellyfin.Server [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string DataDir { get; set; } + [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web resources.")] + public string WebDir { get; set; } + [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] public string CacheDir { get; set; } From 25deca95793581df85dab8c454d53a1d07a6ab53 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 16:20:46 -0400 Subject: [PATCH 140/166] Make use of WebPath --- Emby.Server.Implementations/ApplicationHost.cs | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ae54606747..05ede0ddab 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(contentRoot)) { - contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths,WebPath, "src"); } var host = new WebHostBuilder() diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 531978e1dd..05df54b115 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.WebDashboard.Api return _serverConfigurationManager.Configuration.DashboardSourcePath; } - return Path.Combine(_serverConfigurationManager.ApplicationPaths.ApplicationResourcesPath, "jellyfin-web", "src"); + return Path.Combine(_serverConfigurationManager.ApplicationPaths.WebPath, "src"); } } From 132ce3ece14ff7ca756c81e0a12172744c5c50aa Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 17:04:18 -0400 Subject: [PATCH 141/166] Add further resources to complete WebPath --- Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs | 6 ++++++ Emby.Server.Implementations/ApplicationHost.cs | 4 +++- Emby.Server.Implementations/ServerApplicationPaths.cs | 6 ++++++ Jellyfin.Server/Program.cs | 2 +- MediaBrowser.Common/Configuration/IApplicationPaths.cs | 6 ++++++ MediaBrowser.Model/System/SystemInfo.cs | 6 ++++++ 6 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 3ee09c9c04..fd9ce3a364 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -35,6 +35,12 @@ namespace Emby.Server.Implementations.AppBase /// The program data path. public string ProgramDataPath { get; private set; } + /// + /// Gets the path to the web resources folder + /// + /// The web resources path. + public string WebPath { get; set; } + /// /// Gets the path to the system folder /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 05ede0ddab..f240ef871a 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(contentRoot)) { - contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths,WebPath, "src"); + contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.WebPath, "src"); } var host = new WebHostBuilder() @@ -921,6 +921,7 @@ namespace Emby.Server.Implementations logger.LogInformation("User Interactive: {IsUserInteractive}", Environment.UserInteractive); logger.LogInformation("Processor count: {ProcessorCount}", Environment.ProcessorCount); logger.LogInformation("Program data path: {ProgramDataPath}", appPaths.ProgramDataPath); + logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath); logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } @@ -1393,6 +1394,7 @@ namespace Emby.Server.Implementations CompletedInstallations = InstallationManager.CompletedInstallations.ToArray(), Id = SystemId, ProgramDataPath = ApplicationPaths.ProgramDataPath, + WebPath = ApplicationPaths.WebPath, LogPath = ApplicationPaths.LogDirectoryPath, ItemsByNamePath = ApplicationPaths.InternalMetadataPath, InternalMetadataPath = ApplicationPaths.InternalMetadataPath, diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index adaf23234f..68d5881220 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -35,6 +35,12 @@ namespace Emby.Server.Implementations /// The root folder path. public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); +/* /// + /// Gets the path to the Web resources + /// + /// The web folder path. + public string WebPath => WebPath; */ + /// /// Gets the path to the default user view directory. Used if no specific user view is defined. /// diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 3d81ade35c..8f1498b13b 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -277,7 +277,7 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(webDir)) { // Use default location under ResourcesPath - webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web") + webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web"); } } diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index cb4e8bf5f0..1f56c98fd8 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -11,6 +11,12 @@ namespace MediaBrowser.Common.Configuration /// The program data path. string ProgramDataPath { get; } + /// + /// Gets the path to the web resources folder + /// + /// The web resources path. + string WebPath { get; } + /// /// Gets the path to the program system folder /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index 6482f2c840..fa72624819 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -83,6 +83,12 @@ namespace MediaBrowser.Model.System /// The program data path. public string ProgramDataPath { get; set; } + /// + /// Gets or sets the web resources path. + /// + /// The web resources path. + public string WebPath { get; set; } + /// /// Gets or sets the items by name path. /// From ecf85a73ec4aa918dc9a816dc60e6e5d76a36ecd Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 17:09:51 -0400 Subject: [PATCH 142/166] Use environment variables instead of opts for Deb --- deployment/debian-package-x64/pkg-src/conf/jellyfin | 10 +++++----- deployment/debian-package-x64/pkg-src/jellyfin.service | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin index 58fe79332a..7db482d428 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin @@ -13,10 +13,10 @@ # # Program directories -JELLYFIN_DATA_DIRECTORY="/var/lib/jellyfin" -JELLYFIN_CONFIG_DIRECTORY="/etc/jellyfin" -JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin" -JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin" +JELLYFIN_DATA_DIR="/var/lib/jellyfin" +JELLYFIN_CONFIG_DIR="/etc/jellyfin" +JELLYFIN_LOG_DIR="/var/log/jellyfin" +JELLYFIN_CACHE_DIR="/var/cache/jellyfin" # Restart script for in-app server control JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh" @@ -38,4 +38,4 @@ JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/share/jellyfin-ffmpeg/ffprobe" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="--datadir=$JELLYFIN_DATA_DIRECTORY --configdir=$JELLYFIN_CONFIG_DIRECTORY --logdir=$JELLYFIN_LOG_DIRECTORY --cachedir=$JELLYFIN_CACHE_DIRECTORY $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" +JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/deployment/debian-package-x64/pkg-src/jellyfin.service index 9c6c6667f1..b4da3a945b 100644 --- a/deployment/debian-package-x64/pkg-src/jellyfin.service +++ b/deployment/debian-package-x64/pkg-src/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} Restart = on-failure TimeoutSec = 15 From 86f5221f96b09bde177c881eb34cad0ec10cbe90 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 17:11:16 -0400 Subject: [PATCH 143/166] Use environment variables instead of opts for RPM --- deployment/fedora-package-x64/pkg-src/jellyfin.env | 8 ++++---- deployment/fedora-package-x64/pkg-src/jellyfin.service | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/deployment/fedora-package-x64/pkg-src/jellyfin.env index abfa670e4a..143a317c43 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.env +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.env @@ -15,10 +15,10 @@ # # Program directories -JELLYFIN_DATA_DIRECTORY="/var/lib/jellyfin" -JELLYFIN_CONFIG_DIRECTORY="/etc/jellyfin" -JELLYFIN_LOG_DIRECTORY="/var/log/jellyfin" -JELLYFIN_CACHE_DIRECTORY="/var/cache/jellyfin" +JELLYFIN_DATA_DIR="/var/lib/jellyfin" +JELLYFIN_CONFIG_DIR="/etc/jellyfin" +JELLYFIN_LOG_DIR="/var/log/jellyfin" +JELLYFIN_CACHE_DIR="/var/cache/jellyfin" # In-App service control JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/deployment/fedora-package-x64/pkg-src/jellyfin.service index d58df9d949..1f83d3d386 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.service +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.service @@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of [Service] EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin -ExecStart=/usr/bin/jellyfin --datadir=${JELLYFIN_DATA_DIRECTORY} --configdir=${JELLYFIN_CONFIG_DIRECTORY} --logdir=${JELLYFIN_LOG_DIRECTORY} --cachedir=${JELLYFIN_CACHE_DIRECTORY} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} TimeoutSec=15 Restart=on-failure User=jellyfin From 5f7524aca26797a3dc7acbed8cd50e113e2f9a4d Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 17:21:10 -0400 Subject: [PATCH 144/166] Remove unneccessary string --- Emby.Server.Implementations/ServerApplicationPaths.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Emby.Server.Implementations/ServerApplicationPaths.cs b/Emby.Server.Implementations/ServerApplicationPaths.cs index 68d5881220..adaf23234f 100644 --- a/Emby.Server.Implementations/ServerApplicationPaths.cs +++ b/Emby.Server.Implementations/ServerApplicationPaths.cs @@ -35,12 +35,6 @@ namespace Emby.Server.Implementations /// The root folder path. public string RootFolderPath => Path.Combine(ProgramDataPath, "root"); -/* /// - /// Gets the path to the Web resources - /// - /// The web folder path. - public string WebPath => WebPath; */ - /// /// Gets the path to the default user view directory. Used if no specific user view is defined. /// From 5268553e7fae2975ede06a944944f3b508453a1a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 18:24:11 -0400 Subject: [PATCH 145/166] Have datadir envvar match the others --- Jellyfin.Server/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 0ef1711d4c..c2268c8025 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -174,7 +174,7 @@ namespace Jellyfin.Server { // dataDir // IF --datadir - // ELSE IF $JELLYFIN_DATA_PATH + // ELSE IF $JELLYFIN_DATA_DIR // ELSE IF windows, use <%APPDATA%>/jellyfin // ELSE IF $XDG_DATA_HOME then use $XDG_DATA_HOME/jellyfin // ELSE use $HOME/.local/share/jellyfin @@ -182,7 +182,7 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(dataDir)) { - dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_PATH"); + dataDir = Environment.GetEnvironmentVariable("JELLYFIN_DATA_DIR"); if (string.IsNullOrEmpty(dataDir)) { From 037cf9e1eeb4e500b4bdc379c76fe5a5922c7f54 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Sun, 10 Mar 2019 18:30:10 -0400 Subject: [PATCH 146/166] Move CreateDirectory for dataDir to try block --- Jellyfin.Server/Program.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index c2268c8025..fa4b7b8e52 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -191,8 +191,6 @@ namespace Jellyfin.Server } } - Directory.CreateDirectory(dataDir); - // configDir // IF --configdir // ELSE IF $JELLYFIN_CONFIG_DIR @@ -285,6 +283,7 @@ namespace Jellyfin.Server // Ensure the main folders exist before we continue try { + Directory.CreateDirectory(dataDir); Directory.CreateDirectory(logDir); Directory.CreateDirectory(configDir); Directory.CreateDirectory(cacheDir); From 7322485a6d264037f2e26bf40bac93d6598e7bff Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Mon, 11 Mar 2019 20:44:12 +0100 Subject: [PATCH 147/166] Skip processing of images that don't exist --- Emby.Drawing/ImageProcessor.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index faaeb5af84..6d209d8d01 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -180,6 +180,12 @@ namespace Emby.Drawing var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false); originalImagePath = supportedImageInfo.path; + + if (!File.Exists(originalImagePath)) + { + return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); + } + dateModified = supportedImageInfo.dateModified; bool requiresTransparency = TransparentImageTypes.Contains(Path.GetExtension(originalImagePath)); @@ -265,8 +271,6 @@ namespace Emby.Drawing { // If it fails for whatever reason, return the original image _logger.LogError(ex, "Error encoding image"); - - // Just spit out the original file if all the options are default return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified); } finally From 0ff038f0a2c90ba0ad671eae1ac3c74a7602b855 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 11 Mar 2019 23:13:01 +0100 Subject: [PATCH 148/166] Fix nullref --- Emby.Server.Implementations/ApplicationHost.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index ae54606747..d7696393db 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -700,6 +700,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ApplicationPaths); + serviceCollection.AddSingleton(_configuration); + serviceCollection.AddSingleton(JsonSerializer); serviceCollection.AddSingleton(LoggerFactory); From 715ddbb3b08cab2ff64919f577d5f72d9af5ea22 Mon Sep 17 00:00:00 2001 From: dkanada Date: Mon, 11 Mar 2019 18:10:31 -0700 Subject: [PATCH 149/166] remove open subtitles from the server --- .../Emby.Server.Implementations.csproj | 1 - .../MediaBrowser.MediaEncoding.csproj | 1 - .../Subtitles/ConfigurationExtension.cs | 29 - .../Subtitles/OpenSubtitleDownloader.cs | 347 --- MediaBrowser.sln | 2 - OpenSubtitlesHandler/Console/OSHConsole.cs | 92 - .../Interfaces/IMethodResponse.cs | 71 - .../Interfaces/MethodResponseAttr.cs | 40 - .../Languages/DetectLanguageResult.cs | 31 - .../MethodResponseAddComment.cs | 32 - .../MethodResponseAddRequest.cs | 35 - .../MethodResponseAutoUpdate.cs | 59 - .../MethodResponseCheckMovieHash.cs | 37 - .../MethodResponseCheckMovieHash2.cs | 37 - .../MethodResponseCheckSubHash.cs | 38 - .../MethodResponseDetectLanguage.cs | 37 - .../MethodResponses/MethodResponseError.cs | 36 - .../MethodResponseGetAvailableTranslations.cs | 37 - .../MethodResponseGetComments.cs | 39 - .../MethodResponseGetSubLanguages.cs | 39 - .../MethodResponseGetTranslation.cs | 36 - .../MethodResponseInsertMovie.cs | 36 - .../MethodResponseInsertMovieHash.cs | 38 - .../MethodResponses/MethodResponseLogIn.cs | 39 - .../MethodResponseMovieDetails.cs | 73 - .../MethodResponseMovieSearch.cs | 43 - .../MethodResponseNoOperation.cs | 52 - .../MethodResponseReportWrongImdbMovie.cs | 33 - .../MethodResponseReportWrongMovieHash.cs | 33 - .../MethodResponseSearchToMail.cs | 32 - .../MethodResponseServerInfo.cs | 131 - .../MethodResponseSubtitleDownload.cs | 44 - .../MethodResponseSubtitleSearch.cs | 46 - .../MethodResponseSubtitlesVote.cs | 43 - .../MethodResponseTryUploadSubtitles.cs | 40 - .../MethodResponseUploadSubtitles.cs | 38 - OpenSubtitlesHandler/Methods Implemeted.txt | 39 - OpenSubtitlesHandler/MovieHasher.cs | 48 - .../Movies/CheckMovieHash2Data.cs | 42 - .../Movies/CheckMovieHash2Result.cs | 37 - .../Movies/CheckMovieHashResult.cs | 41 - .../Movies/InsertMovieHashParameters.cs | 38 - .../Movies/MovieSearchResult.cs | 40 - .../Movies/SearchToMailMovieParameter.cs | 30 - OpenSubtitlesHandler/OpenSubtitles.cs | 2744 ----------------- .../OpenSubtitlesHandler.csproj | 13 - .../GetAvailableTranslationsResult.cs | 39 - .../OtherTypes/GetCommentsResult.cs | 36 - .../Properties/AssemblyInfo.cs | 24 - OpenSubtitlesHandler/Readme.txt | 20 - .../SubtitleTypes/CheckSubHashResult.cs | 30 - .../SubtitleTypes/SubtitleDownloadResult.cs | 43 - .../SubtitleTypes/SubtitleLanguage.cs | 39 - .../SubtitleTypes/SubtitleSearchParameters.cs | 82 - .../SubtitleTypes/SubtitleSearchResult.cs | 136 - .../TryUploadSubtitlesParameters.cs | 47 - .../UploadSubtitleInfoParameter.cs | 46 - .../SubtitleTypes/UploadSubtitleParameters.cs | 52 - OpenSubtitlesHandler/Utilities.cs | 174 -- OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt | 225 -- .../XML-RPC/Enums/XmlRpcValueType.cs | 25 - .../XML-RPC/Types/XmlRpcMethodCall.cs | 63 - .../XML-RPC/Values/IXmlRpcValue.cs | 36 - .../XML-RPC/Values/XmlRpcStructMember.cs | 43 - .../XML-RPC/Values/XmlRpcValueArray.cs | 121 - .../XML-RPC/Values/XmlRpcValueBasic.cs | 99 - .../XML-RPC/Values/XmlRpcValueStruct.cs | 43 - .../XML-RPC/XmlRpcGenerator.cs | 350 --- 68 files changed, 6642 deletions(-) delete mode 100644 MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs delete mode 100644 MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs delete mode 100644 OpenSubtitlesHandler/Console/OSHConsole.cs delete mode 100644 OpenSubtitlesHandler/Interfaces/IMethodResponse.cs delete mode 100644 OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs delete mode 100644 OpenSubtitlesHandler/Languages/DetectLanguageResult.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs delete mode 100644 OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs delete mode 100644 OpenSubtitlesHandler/Methods Implemeted.txt delete mode 100644 OpenSubtitlesHandler/MovieHasher.cs delete mode 100644 OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs delete mode 100644 OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs delete mode 100644 OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs delete mode 100644 OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs delete mode 100644 OpenSubtitlesHandler/Movies/MovieSearchResult.cs delete mode 100644 OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs delete mode 100644 OpenSubtitlesHandler/OpenSubtitles.cs delete mode 100644 OpenSubtitlesHandler/OpenSubtitlesHandler.csproj delete mode 100644 OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs delete mode 100644 OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs delete mode 100644 OpenSubtitlesHandler/Properties/AssemblyInfo.cs delete mode 100644 OpenSubtitlesHandler/Readme.txt delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs delete mode 100644 OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs delete mode 100644 OpenSubtitlesHandler/Utilities.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt delete mode 100644 OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs delete mode 100644 OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 39e9ed3756..0b0dab2139 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -13,7 +13,6 @@ - diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 68b8bd4fae..e4757543ea 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -14,7 +14,6 @@ - diff --git a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs b/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs deleted file mode 100644 index 92544f4f62..0000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/ConfigurationExtension.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Providers; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public static class ConfigurationExtension - { - public static SubtitleOptions GetSubtitleConfiguration(this IConfigurationManager manager) - { - return manager.GetConfiguration("subtitles"); - } - } - - public class SubtitleConfigurationFactory : IConfigurationFactory - { - public IEnumerable GetConfigurations() - { - return new List - { - new ConfigurationStore - { - Key = "subtitles", - ConfigurationType = typeof (SubtitleOptions) - } - }; - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs b/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs deleted file mode 100644 index a7e3f61972..0000000000 --- a/MediaBrowser.MediaEncoding/Subtitles/OpenSubtitleDownloader.cs +++ /dev/null @@ -1,347 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Subtitles; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; -using Microsoft.Extensions.Logging; -using OpenSubtitlesHandler; - -namespace MediaBrowser.MediaEncoding.Subtitles -{ - public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable - { - private readonly ILogger _logger; - private readonly IHttpClient _httpClient; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - private readonly IServerConfigurationManager _config; - - private readonly IJsonSerializer _json; - private readonly IFileSystem _fileSystem; - - public OpenSubtitleDownloader(ILoggerFactory loggerFactory, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem) - { - _logger = loggerFactory.CreateLogger(GetType().Name); - _httpClient = httpClient; - _config = config; - _json = json; - _fileSystem = fileSystem; - - _config.NamedConfigurationUpdating += _config_NamedConfigurationUpdating; - - Utilities.HttpClient = httpClient; - OpenSubtitles.SetUserAgent("jellyfin"); - } - - private const string PasswordHashPrefix = "h:"; - void _config_NamedConfigurationUpdating(object sender, ConfigurationUpdateEventArgs e) - { - if (!string.Equals(e.Key, "subtitles", StringComparison.OrdinalIgnoreCase)) - { - return; - } - - var options = (SubtitleOptions)e.NewConfiguration; - - if (options != null && - !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) && - !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - options.OpenSubtitlesPasswordHash = EncodePassword(options.OpenSubtitlesPasswordHash); - } - } - - private static string EncodePassword(string password) - { - var bytes = Encoding.UTF8.GetBytes(password); - return PasswordHashPrefix + Convert.ToBase64String(bytes); - } - - private static string DecodePassword(string password) - { - if (password == null || - !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase)) - { - return string.Empty; - } - - var bytes = Convert.FromBase64String(password.Substring(2)); - return Encoding.UTF8.GetString(bytes, 0, bytes.Length); - } - - public string Name => "Open Subtitles"; - - private SubtitleOptions GetOptions() - { - return _config.GetSubtitleConfiguration(); - } - - public IEnumerable SupportedMediaTypes - { - get - { - var options = GetOptions(); - - if (string.IsNullOrWhiteSpace(options.OpenSubtitlesUsername) || - string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash)) - { - return new VideoContentType[] { }; - } - - return new[] { VideoContentType.Episode, VideoContentType.Movie }; - } - } - - public Task GetSubtitles(string id, CancellationToken cancellationToken) - { - return GetSubtitlesInternal(id, GetOptions(), cancellationToken); - } - - private DateTime _lastRateLimitException; - private async Task GetSubtitlesInternal(string id, - SubtitleOptions options, - CancellationToken cancellationToken) - { - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentNullException(nameof(id)); - } - - var idParts = id.Split(new[] { '-' }, 3); - - var format = idParts[0]; - var language = idParts[1]; - var ossId = idParts[2]; - - var downloadsList = new[] { int.Parse(ossId, _usCulture) }; - - await Login(cancellationToken).ConfigureAwait(false); - - if ((DateTime.UtcNow - _lastRateLimitException).TotalHours < 1) - { - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false); - - if ((resultDownLoad.Status ?? string.Empty).IndexOf("407", StringComparison.OrdinalIgnoreCase) != -1) - { - _lastRateLimitException = DateTime.UtcNow; - throw new RateLimitExceededException("OpenSubtitles rate limit reached"); - } - - if (!(resultDownLoad is MethodResponseSubtitleDownload)) - { - throw new Exception("Invalid response type"); - } - - var results = ((MethodResponseSubtitleDownload)resultDownLoad).Results; - - _lastRateLimitException = DateTime.MinValue; - - if (results.Count == 0) - { - var msg = string.Format("Subtitle with Id {0} was not found. Name: {1}. Status: {2}. Message: {3}", - ossId, - resultDownLoad.Name ?? string.Empty, - resultDownLoad.Status ?? string.Empty, - resultDownLoad.Message ?? string.Empty); - - throw new ResourceNotFoundException(msg); - } - - var data = Convert.FromBase64String(results.First().Data); - - return new SubtitleResponse - { - Format = format, - Language = language, - - Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data))) - }; - } - - private DateTime _lastLogin; - private async Task Login(CancellationToken cancellationToken) - { - if ((DateTime.UtcNow - _lastLogin).TotalSeconds < 60) - { - return; - } - - var options = GetOptions(); - - var user = options.OpenSubtitlesUsername ?? string.Empty; - var password = DecodePassword(options.OpenSubtitlesPasswordHash); - - var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false); - - if (!(loginResponse is MethodResponseLogIn)) - { - throw new Exception("Authentication to OpenSubtitles failed."); - } - - _lastLogin = DateTime.UtcNow; - } - - public async Task> GetSupportedLanguages(CancellationToken cancellationToken) - { - await Login(cancellationToken).ConfigureAwait(false); - - var result = OpenSubtitles.GetSubLanguages("en"); - if (!(result is MethodResponseGetSubLanguages)) - { - _logger.LogError("Invalid response type"); - return new List(); - } - - var results = ((MethodResponseGetSubLanguages)result).Languages; - - return results.Select(i => new NameIdPair - { - Name = i.LanguageName, - Id = i.SubLanguageID - }); - } - - private string NormalizeLanguage(string language) - { - // Problem with Greek subtitle download #1349 - if (string.Equals(language, "gre", StringComparison.OrdinalIgnoreCase)) - { - - return "ell"; - } - - return language; - } - - public async Task> Search(SubtitleSearchRequest request, CancellationToken cancellationToken) - { - var imdbIdText = request.GetProviderId(MetadataProviders.Imdb); - long imdbId = 0; - - switch (request.ContentType) - { - case VideoContentType.Episode: - if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName)) - { - _logger.LogDebug("Episode information missing"); - return new List(); - } - break; - case VideoContentType.Movie: - if (string.IsNullOrEmpty(request.Name)) - { - _logger.LogDebug("Movie name missing"); - return new List(); - } - if (string.IsNullOrWhiteSpace(imdbIdText) || !long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId)) - { - _logger.LogDebug("Imdb id missing"); - return new List(); - } - break; - } - - if (string.IsNullOrEmpty(request.MediaPath)) - { - _logger.LogDebug("Path Missing"); - return new List(); - } - - await Login(cancellationToken).ConfigureAwait(false); - - var subLanguageId = NormalizeLanguage(request.Language); - string hash; - - using (var fileStream = File.OpenRead(request.MediaPath)) - { - hash = Utilities.ComputeHash(fileStream); - } - var fileInfo = _fileSystem.GetFileInfo(request.MediaPath); - var movieByteSize = fileInfo.Length; - var searchImdbId = request.ContentType == VideoContentType.Movie ? imdbId.ToString(_usCulture) : ""; - var subtitleSearchParameters = request.ContentType == VideoContentType.Episode - ? new List { - new SubtitleSearchParameters(subLanguageId, - query: request.SeriesName, - season: request.ParentIndexNumber.Value.ToString(_usCulture), - episode: request.IndexNumber.Value.ToString(_usCulture)) - } - : new List { - new SubtitleSearchParameters(subLanguageId, imdbid: searchImdbId), - new SubtitleSearchParameters(subLanguageId, query: request.Name, imdbid: searchImdbId) - }; - var parms = new List { - new SubtitleSearchParameters( subLanguageId, - movieHash: hash, - movieByteSize: movieByteSize, - imdbid: searchImdbId ), - }; - parms.AddRange(subtitleSearchParameters); - var result = await OpenSubtitles.SearchSubtitlesAsync(parms.ToArray(), cancellationToken).ConfigureAwait(false); - if (!(result is MethodResponseSubtitleSearch)) - { - _logger.LogError("Invalid response type"); - return new List(); - } - - Predicate mediaFilter = - x => - request.ContentType == VideoContentType.Episode - ? !string.IsNullOrEmpty(x.SeriesSeason) && !string.IsNullOrEmpty(x.SeriesEpisode) && - int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && - int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber - : !string.IsNullOrEmpty(x.IDMovieImdb) && long.Parse(x.IDMovieImdb, _usCulture) == imdbId; - - var results = ((MethodResponseSubtitleSearch)result).Results; - - // Avoid implicitly captured closure - var hasCopy = hash; - - return results.Where(x => x.SubBad == "0" && mediaFilter(x) && (!request.IsPerfectMatch || string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase))) - .OrderBy(x => (string.Equals(x.MovieHash, hash, StringComparison.OrdinalIgnoreCase) ? 0 : 1)) - .ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize)) - .ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture)) - .ThenByDescending(x => double.Parse(x.SubRating, _usCulture)) - .Select(i => new RemoteSubtitleInfo - { - Author = i.UserNickName, - Comment = i.SubAuthorComment, - CommunityRating = float.Parse(i.SubRating, _usCulture), - DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture), - Format = i.SubFormat, - ProviderName = Name, - ThreeLetterISOLanguageName = i.SubLanguageID, - - Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitleFile, - - Name = i.SubFileName, - DateCreated = DateTime.Parse(i.SubAddDate, _usCulture), - IsHashMatch = i.MovieHash == hasCopy - - }).Where(i => !string.Equals(i.Format, "sub", StringComparison.OrdinalIgnoreCase) && !string.Equals(i.Format, "idx", StringComparison.OrdinalIgnoreCase)); - } - - public void Dispose() - { - _config.NamedConfigurationUpdating -= _config_NamedConfigurationUpdating; - } - } -} diff --git a/MediaBrowser.sln b/MediaBrowser.sln index a3c0569bad..3ed86d65c7 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -15,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.WebDashboard", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.Providers", "MediaBrowser.Providers\MediaBrowser.Providers.csproj", "{442B5058-DCAF-4263-BB6A-F21E31120A1B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSubtitlesHandler", "OpenSubtitlesHandler\OpenSubtitlesHandler.csproj", "{4A4402D4-E910-443B-B8FC-2C18286A2CA0}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.XbmcMetadata", "MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj", "{23499896-B135-4527-8574-C26E926EA99E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.LocalMetadata", "MediaBrowser.LocalMetadata\MediaBrowser.LocalMetadata.csproj", "{7EF9F3E0-697D-42F3-A08F-19DEB5F84392}" diff --git a/OpenSubtitlesHandler/Console/OSHConsole.cs b/OpenSubtitlesHandler/Console/OSHConsole.cs deleted file mode 100644 index 396b28cbc6..0000000000 --- a/OpenSubtitlesHandler/Console/OSHConsole.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System; - -namespace OpenSubtitlesHandler.Console -{ - public class OSHConsole - { - /// - /// Write line to the console and raise the "LineWritten" event - /// - /// - /// The debug line - /// The status - public static void WriteLine(string text, DebugCode code = DebugCode.None) - { - if (LineWritten != null) - LineWritten(null, new DebugEventArgs(text, code)); - } - /// - /// Update the last written line - /// - /// The debug line - /// The status - public static void UpdateLine(string text, DebugCode code = DebugCode.None) - { - if (UpdateLastLine != null) - UpdateLastLine(null, new DebugEventArgs(text, code)); - } - - public static event EventHandler LineWritten; - public static event EventHandler UpdateLastLine; - } - public enum DebugCode - { - None, - Good, - Warning, - Error - } - /// - /// Console Debug Args - /// - public class DebugEventArgs : EventArgs - { - public DebugCode Code { get; private set; } - public string Text { get; private set; } - - /// - /// Console Debug Args - /// - /// The debug line - /// The status - public DebugEventArgs(string text, DebugCode code) - { - this.Text = text; - this.Code = code; - } - } - public struct DebugLine - { - public DebugLine(string debugLine, DebugCode status) - { - this.debugLine = debugLine; - this.status = status; - } - string debugLine; - DebugCode status; - - public string Text - { get { return debugLine; } set { debugLine = value; } } - public DebugCode Code - { get { return status; } set { status = value; } } - } -} diff --git a/OpenSubtitlesHandler/Interfaces/IMethodResponse.cs b/OpenSubtitlesHandler/Interfaces/IMethodResponse.cs deleted file mode 100644 index 3450beff59..0000000000 --- a/OpenSubtitlesHandler/Interfaces/IMethodResponse.cs +++ /dev/null @@ -1,71 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; - -namespace OpenSubtitlesHandler -{ - /// - /// When you call a method to communicate with OpenSubtitles server, that method should return this response with the reuired information. - /// - public abstract class IMethodResponse - { - public IMethodResponse() { LoadAttributes(); } - public IMethodResponse(string name, string message) - { - this.name = name; - this.message = message; - } - protected string name; - protected string message; - protected double seconds; - protected string status; - - protected void LoadAttributes() - { - //foreach (Attribute attr in Attribute.GetCustomAttributes(this.GetType())) - //{ - // if (attr.GetType() == typeof(MethodResponseDescription)) - // { - // this.name = ((MethodResponseDescription)attr).Name; - // this.message = ((MethodResponseDescription)attr).Message; - // break; - // } - //} - } - - [Description("The name of this response"), Category("MethodResponse")] - public virtual string Name { get { return name; } set { name = value; } } - [Description("The message about this response"), Category("MethodResponse")] - public virtual string Message { get { return message; } set { message = value; } } - [Description("Time taken to execute this command on server"), Category("MethodResponse")] - public double Seconds { get { return seconds; } set { seconds = value; } } - [Description("The status"), Category("MethodResponse")] - public string Status { get { return status; } set { status = value; } } - } - - public class DescriptionAttribute : Attribute - { - public DescriptionAttribute(string text) { } - } - - public class CategoryAttribute : Attribute - { - public CategoryAttribute(string text) { } - } -} diff --git a/OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs b/OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs deleted file mode 100644 index a7e49032de..0000000000 --- a/OpenSubtitlesHandler/Interfaces/MethodResponseAttr.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; - -namespace OpenSubtitlesHandler -{ - /// - /// Attributes to describe a method response object. - /// - public class MethodResponseDescription : Attribute - { - public MethodResponseDescription(string name, string message) - { - this.name = name; - this.message = message; - } - - private string name; - private string message; - - public string Name { get { return name; } set { name = value; } } - public string Message { get { return message; } set { message = value; } } - } -} diff --git a/OpenSubtitlesHandler/Languages/DetectLanguageResult.cs b/OpenSubtitlesHandler/Languages/DetectLanguageResult.cs deleted file mode 100644 index 3e72dc65c4..0000000000 --- a/OpenSubtitlesHandler/Languages/DetectLanguageResult.cs +++ /dev/null @@ -1,31 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - public struct DetectLanguageResult - { - private string inputSample; - private string languageID; - - public string LanguageID - { get { return languageID; } set { languageID = value; } } - public string InputSample - { get { return inputSample; } set { inputSample = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs deleted file mode 100644 index 03d0337d4c..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddComment.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("AddComment method response", - "AddComment method response hold all expected values from server.")] - public class MethodResponseAddComment : IMethodResponse - { - public MethodResponseAddComment() - : base() - { } - public MethodResponseAddComment(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs deleted file mode 100644 index b996043c28..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseAddRequest.cs +++ /dev/null @@ -1,35 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("AddRequest method response", - "AddRequest method response hold all expected values from server.")] - public class MethodResponseAddRequest : IMethodResponse - { - public MethodResponseAddRequest() - : base() - { } - public MethodResponseAddRequest(string name, string message) - : base(name, message) - { } - private string _request_url; - - public string request_url { get { return _request_url; } set { _request_url = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs deleted file mode 100644 index 6ee14f1f8f..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseAutoUpdate.cs +++ /dev/null @@ -1,59 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("AutoUpdate method response", - "AutoUpdate method response hold all expected values from server.")] - public class MethodResponseAutoUpdate : IMethodResponse - { - public MethodResponseAutoUpdate() - : base() - { } - public MethodResponseAutoUpdate(string name, string message) - : base(name, message) - { } - - private string _version; - private string _url_windows; - private string _comments; - private string _url_linux; - /// - /// Latest application version - /// - [Description("Latest application version"), Category("AutoUpdate")] - public string version { get { return _version; } set { _version = value; } } - /// - /// Download URL for Windows version - /// - [Description("Download URL for Windows version"), Category("AutoUpdate")] - public string url_windows { get { return _url_windows; } set { _url_windows = value; } } - /// - /// Application changelog and other comments - /// - [Description("Application changelog and other comments"), Category("AutoUpdate")] - public string comments { get { return _comments; } set { _comments = value; } } - /// - /// Download URL for Linux version - /// - [Description("Download URL for Linux version"), Category("AutoUpdate")] - public string url_linux { get { return _url_linux; } set { _url_linux = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs deleted file mode 100644 index 4bb3262442..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("CheckMovieHash method response", - "CheckMovieHash method response hold all expected values from server.")] - public class MethodResponseCheckMovieHash : IMethodResponse - { - public MethodResponseCheckMovieHash() - : base() - { } - public MethodResponseCheckMovieHash(string name, string message) - : base(name, message) - { } - private List results = new List(); - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs deleted file mode 100644 index 72b4c3b152..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckMovieHash2.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("CheckMovieHash2 method response", - "CheckMovieHash2 method response hold all expected values from server.")] - public class MethodResponseCheckMovieHash2 : IMethodResponse - { - public MethodResponseCheckMovieHash2() - : base() - { } - public MethodResponseCheckMovieHash2(string name, string message) - : base(name, message) - { } - private List results = new List(); - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs deleted file mode 100644 index 04e287ea74..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseCheckSubHash.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("CheckSubHash method response", - "CheckSubHash method response hold all expected values from server.")] - public class MethodResponseCheckSubHash : IMethodResponse - { - public MethodResponseCheckSubHash() - : base() - { } - public MethodResponseCheckSubHash(string name, string message) - : base(name, message) - { } - private List results = new List(); - - public List Results { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs deleted file mode 100644 index 6bf21d50e8..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseDetectLanguage.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("DetectLanguage method response", - "DetectLanguage method response hold all expected values from server.")] - public class MethodResponseDetectLanguage : IMethodResponse - { - public MethodResponseDetectLanguage() - : base() - { } - public MethodResponseDetectLanguage(string name, string message) - : base(name, message) - { } - private List results = new List(); - - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs deleted file mode 100644 index 7ed807067e..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseError.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// Response that can be used for general error like internet connection fail. - /// - [MethodResponseDescription("Error method response", - "Error method response that describes error that occured")] - public class MethodResponseError : IMethodResponse - { - public MethodResponseError() - : base() - { } - public MethodResponseError(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs deleted file mode 100644 index 91803f4b3d..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetAvailableTranslations.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetAvailableTranslations method response", - "GetAvailableTranslations method response hold all expected values from server.")] - public class MethodResponseGetAvailableTranslations : IMethodResponse - { - public MethodResponseGetAvailableTranslations() - : base() - { } - public MethodResponseGetAvailableTranslations(string name, string message) - : base(name, message) - { } - private List results = new List(); - public List Results { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs deleted file mode 100644 index 421e1783bc..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetComments.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetComments method response", - "GetComments method response hold all expected values from server.")] - public class MethodResponseGetComments : IMethodResponse - { - public MethodResponseGetComments() - : base() - { } - public MethodResponseGetComments(string name, string message) - : base(name, message) - { } - private List results = new List(); - - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs deleted file mode 100644 index 905b87c916..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetSubLanguages.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetSubLanguages method response", - "GetSubLanguages method response hold all expected values from server.")] - public class MethodResponseGetSubLanguages : IMethodResponse - { - public MethodResponseGetSubLanguages() - : base() - { } - public MethodResponseGetSubLanguages(string name, string message) - : base(name, message) - { } - private List languages = new List(); - - public List Languages - { get { return languages; } set { languages = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs deleted file mode 100644 index f008f7711d..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseGetTranslation.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("GetTranslation method response", - "GetTranslation method response hold all expected values from server.")] - public class MethodResponseGetTranslation : IMethodResponse - { - public MethodResponseGetTranslation() - : base() - { } - public MethodResponseGetTranslation(string name, string message) - : base(name, message) - { } - private string data; - - public string ContentData { get { return data; } set { data = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs deleted file mode 100644 index 1148b5f479..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovie.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("InsertMovie method response", - "InsertMovie method response hold all expected values from server.")] - public class MethodResponseInsertMovie : IMethodResponse - { - public MethodResponseInsertMovie() - : base() - { } - public MethodResponseInsertMovie(string name, string message) - : base(name, message) - { } - private string _ID; - public string ID - { get { return _ID; } set { _ID = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs deleted file mode 100644 index 74f52837c9..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseInsertMovieHash.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("InsertMovieHash method response", - "InsertMovieHash method response hold all expected values from server.")] - public class MethodResponseInsertMovieHash : IMethodResponse - { - public MethodResponseInsertMovieHash() - : base() - { } - public MethodResponseInsertMovieHash(string name, string message) - : base(name, message) - { } - private List _accepted_moviehashes = new List(); - private List _new_imdbs = new List(); - - public List accepted_moviehashes { get { return _accepted_moviehashes; } set { _accepted_moviehashes = value; } } - public List new_imdbs { get { return _new_imdbs; } set { _new_imdbs = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs deleted file mode 100644 index 44d2943824..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseLogIn.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// Response can be used for log in/out operation. - /// - [MethodResponseDescription("LogIn method response", - "LogIn method response hold all expected values from server.")] - public class MethodResponseLogIn : IMethodResponse - { - public MethodResponseLogIn() - : base() - { } - public MethodResponseLogIn(string name, string message) - : base(name, message) - { } - private string token; - - public string Token { get { return token; } set { token = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs deleted file mode 100644 index 6126c0053a..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieDetails.cs +++ /dev/null @@ -1,73 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("MovieDetails method response", - "MovieDetails method response hold all expected values from server.")] - public class MethodResponseMovieDetails : IMethodResponse - { - public MethodResponseMovieDetails() - : base() - { } - public MethodResponseMovieDetails(string name, string message) - : base(name, message) - { } - // Details - private string id; - private string title; - private string year; - private string coverLink; - - private string duration; - private string tagline; - private string plot; - private string goofs; - private string trivia; - private List cast = new List(); - private List directors = new List(); - private List writers = new List(); - private List awards = new List(); - private List genres = new List(); - private List country = new List(); - private List language = new List(); - private List certification = new List(); - - // Details - public string ID { get { return id; } set { id = value; } } - public string Title { get { return title; } set { title = value; } } - public string Year { get { return year; } set { year = value; } } - public string CoverLink { get { return coverLink; } set { coverLink = value; } } - public string Duration { get { return duration; } set { duration = value; } } - public string Tagline { get { return tagline; } set { tagline = value; } } - public string Plot { get { return plot; } set { plot = value; } } - public string Goofs { get { return goofs; } set { goofs = value; } } - public string Trivia { get { return trivia; } set { trivia = value; } } - public List Cast { get { return cast; } set { cast = value; } } - public List Directors { get { return directors; } set { directors = value; } } - public List Writers { get { return writers; } set { writers = value; } } - public List Genres { get { return genres; } set { genres = value; } } - public List Awards { get { return awards; } set { awards = value; } } - public List Country { get { return country; } set { country = value; } } - public List Language { get { return language; } set { language = value; } } - public List Certification { get { return certification; } set { certification = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs deleted file mode 100644 index 93cd703462..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseMovieSearch.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("MovieSearch method response", - "MovieSearch method response hold all expected values from server.")] - public class MethodResponseMovieSearch : IMethodResponse - { - public MethodResponseMovieSearch() - : base() - { - results = new List(); - } - public MethodResponseMovieSearch(string name, string message) - : base(name, message) - { - results = new List(); - } - private List results; - - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs deleted file mode 100644 index 02a9993cb1..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseNoOperation.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("NoOperation method response", - "NoOperation method response hold all expected values from server.")] - public class MethodResponseNoOperation : IMethodResponse - { - public MethodResponseNoOperation() - : base() { } - public MethodResponseNoOperation(string name, string message) - : base(name, message) - { } - - private string _global_wrh_download_limit; - private string _client_ip; - private string _limit_check_by; - private string _client_24h_download_count; - private string _client_downlaod_quota; - private string _client_24h_download_limit; - - public string global_wrh_download_limit - { get { return _global_wrh_download_limit; } set { _global_wrh_download_limit = value; } } - public string client_ip - { get { return _client_ip; } set { _client_ip = value; } } - public string limit_check_by - { get { return _limit_check_by; } set { _limit_check_by = value; } } - public string client_24h_download_count - { get { return _client_24h_download_count; } set { _client_24h_download_count = value; } } - public string client_downlaod_quota - { get { return _client_downlaod_quota; } set { _client_downlaod_quota = value; } } - public string client_24h_download_limit - { get { return _client_24h_download_limit; } set { _client_24h_download_limit = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs deleted file mode 100644 index 391fec58a0..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongImdbMovie.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("ReportWrongImdbMovie method response", - "ReportWrongImdbMovie method response hold all expected values from server.")] - public class MethodResponseReportWrongImdbMovie : IMethodResponse - { - public MethodResponseReportWrongImdbMovie() - : base() - { } - public MethodResponseReportWrongImdbMovie(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs deleted file mode 100644 index 5696e7084c..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseReportWrongMovieHash.cs +++ /dev/null @@ -1,33 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("ReportWrongMovieHash method response", - "ReportWrongMovieHash method response hold all expected values from server.")] - public class MethodResponseReportWrongMovieHash : IMethodResponse - { - public MethodResponseReportWrongMovieHash() - : base() - { } - public MethodResponseReportWrongMovieHash(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs deleted file mode 100644 index ea248bc22c..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSearchToMail.cs +++ /dev/null @@ -1,32 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("SearchToMail method response", - "SearchToMail method response hold all expected values from server.")] - public class MethodResponseSearchToMail : IMethodResponse - { - public MethodResponseSearchToMail() - : base() - { } - public MethodResponseSearchToMail(string name, string message) - : base(name, message) - { } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs deleted file mode 100644 index 973550e9f0..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseServerInfo.cs +++ /dev/null @@ -1,131 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("ServerInfo method response", - "ServerInfo method response hold all expected values from server.")] - public class MethodResponseServerInfo : IMethodResponse - { - public MethodResponseServerInfo() - : base() - { } - public MethodResponseServerInfo(string name, string message) - : base(name, message) - { } - private string _xmlrpc_version; - private string _xmlrpc_url; - private string _application; - private string _contact; - private string _website_url; - private int _users_online_total; - private int _users_online_program; - private int _users_loggedin; - private string _users_max_alltime; - private string _users_registered; - private string _subs_downloads; - private string _subs_subtitle_files; - private string _movies_total; - private string _movies_aka; - private string _total_subtitles_languages; - private List _last_update_strings = new List(); - - /// - /// Version of server's XML-RPC API implementation - /// - [Description("Version of server's XML-RPC API implementation"), Category("OS")] - public string xmlrpc_version { get { return _xmlrpc_version; } set { _xmlrpc_version = value; } } - /// - /// XML-RPC interface URL - /// - [Description("XML-RPC interface URL"), Category("OS")] - public string xmlrpc_url { get { return _xmlrpc_url; } set { _xmlrpc_url = value; } } - /// - /// Server's application name and version - /// - [Description("Server's application name and version"), Category("OS")] - public string application { get { return _application; } set { _application = value; } } - /// - /// Contact e-mail address for server related quuestions and problems - /// - [Description("Contact e-mail address for server related quuestions and problems"), Category("OS")] - public string contact { get { return _contact; } set { _contact = value; } } - /// - /// Main server URL - /// - [Description("Main server URL"), Category("OS")] - public string website_url { get { return _website_url; } set { _website_url = value; } } - /// - /// Number of users currently online - /// - [Description("Number of users currently online"), Category("OS")] - public int users_online_total { get { return _users_online_total; } set { _users_online_total = value; } } - /// - /// Number of users currently online using a client application (XML-RPC API) - /// - [Description("Number of users currently online using a client application (XML-RPC API)"), Category("OS")] - public int users_online_program { get { return _users_online_program; } set { _users_online_program = value; } } - /// - /// Number of currently logged-in users - /// - [Description("Number of currently logged-in users"), Category("OS")] - public int users_loggedin { get { return _users_loggedin; } set { _users_loggedin = value; } } - /// - /// Maximum number of users throughout the history - /// - [Description("Maximum number of users throughout the history"), Category("OS")] - public string users_max_alltime { get { return _users_max_alltime; } set { _users_max_alltime = value; } } - /// - /// Number of registered users - /// - [Description("Number of registered users"), Category("OS")] - public string users_registered { get { return _users_registered; } set { _users_registered = value; } } - /// - /// Total number of subtitle downloads - /// - [Description("Total number of subtitle downloads"), Category("OS")] - public string subs_downloads { get { return _subs_downloads; } set { _subs_downloads = value; } } - /// - /// Total number of subtitle files stored on the server - /// - [Description("Total number of subtitle files stored on the server"), Category("OS")] - public string subs_subtitle_files { get { return _subs_subtitle_files; } set { _subs_subtitle_files = value; } } - /// - /// Total number of movies in the database - /// - [Description("Total number of movies in the database"), Category("OS")] - public string movies_total { get { return _movies_total; } set { _movies_total = value; } } - /// - /// Total number of movie A.K.A. titles in the database - /// - [Description("Total number of movie A.K.A. titles in the database"), Category("OS")] - public string movies_aka { get { return _movies_aka; } set { _movies_aka = value; } } - /// - /// Total number of subtitle languages supported - /// - [Description("Total number of subtitle languages supported"), Category("OS")] - public string total_subtitles_languages { get { return _total_subtitles_languages; } set { _total_subtitles_languages = value; } } - /// - /// Structure containing information about last updates of translations. - /// - [Description("Structure containing information about last updates of translations"), Category("OS")] - public List last_update_strings { get { return _last_update_strings; } set { _last_update_strings = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs deleted file mode 100644 index 6a5d57d19a..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleDownload.cs +++ /dev/null @@ -1,44 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; -namespace OpenSubtitlesHandler -{ - /// - /// The response that should be used by subtitle download methods. - /// - [MethodResponseDescription("SubtitleDownload method response", - "SubtitleDownload method response hold all expected values from server.")] - public class MethodResponseSubtitleDownload : IMethodResponse - { - public MethodResponseSubtitleDownload() - : base() - { - results = new List(); - } - public MethodResponseSubtitleDownload(string name, string message) - : base(name, message) - { - results = new List(); - } - private List results; - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs deleted file mode 100644 index 0dce203495..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitleSearch.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - /// - /// Response to SearchSubtitle successed call. - /// - [MethodResponseDescription("SubtitleSearch method response", - "SubtitleSearch method response hold all expected values from server.")] - public class MethodResponseSubtitleSearch : IMethodResponse - { - public MethodResponseSubtitleSearch() - : base() - { - results = new List(); - } - public MethodResponseSubtitleSearch(string name, string message) - : base(name, message) - { - results = new List(); - } - - private List results; - public List Results - { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs deleted file mode 100644 index f02f822f05..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseSubtitlesVote.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("SubtitlesVote method response", - "SubtitlesVote method response hold all expected values from server.")] - public class MethodResponseSubtitlesVote : IMethodResponse - { - public MethodResponseSubtitlesVote() - : base() - { } - public MethodResponseSubtitlesVote(string name, string message) - : base(name, message) - { } - private string _SubRating; - private string _SubSumVotes; - private string _IDSubtitle; - - public string SubRating - { get { return _SubRating; } set { _SubRating = value; } } - public string SubSumVotes - { get { return _SubSumVotes; } set { _SubSumVotes = value; } } - public string IDSubtitle - { get { return _IDSubtitle; } set { _IDSubtitle = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs deleted file mode 100644 index cb3866a628..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseTryUploadSubtitles.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("TryUploadSubtitles method response", - "TryUploadSubtitles method response hold all expected values from server.")] - public class MethodResponseTryUploadSubtitles : IMethodResponse - { - public MethodResponseTryUploadSubtitles() - : base() - { } - public MethodResponseTryUploadSubtitles(string name, string message) - : base(name, message) - { } - private int alreadyindb; - private List results = new List(); - - public int AlreadyInDB { get { return alreadyindb; } set { alreadyindb = value; } } - public List Results { get { return results; } set { results = value; } } - } -} diff --git a/OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs b/OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs deleted file mode 100644 index bda950befc..0000000000 --- a/OpenSubtitlesHandler/MethodResponses/MethodResponseUploadSubtitles.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - [MethodResponseDescription("UploadSubtitles method response", - "UploadSubtitles method response hold all expected values from server.")] - public class MethodResponseUploadSubtitles : IMethodResponse - { - public MethodResponseUploadSubtitles() - : base() - { } - public MethodResponseUploadSubtitles(string name, string message) - : base(name, message) - { } - private string _data; - private bool _subtitles; - - public string Data { get { return _data; } set { _data = value; } } - public bool SubTitles { get { return _subtitles; } set { _subtitles = value; } } - } -} diff --git a/OpenSubtitlesHandler/Methods Implemeted.txt b/OpenSubtitlesHandler/Methods Implemeted.txt deleted file mode 100644 index e3493d9a2c..0000000000 --- a/OpenSubtitlesHandler/Methods Implemeted.txt +++ /dev/null @@ -1,39 +0,0 @@ -List of available OpenSubtitles.org server XML-RPC methods. -========================================================== -Legends: -* OK: this method is fully implemented, tested and works fine. -* TODO: this method is in the plan to be added. -* NOT TESTED: this method added and expected to work fine but never tested. -* NOT WORK (x): this method added but not work. x= Description of the error. - --------------------------------------------- -Method name | Status --------------------------|------------------ -LogIn | OK -LogOut | OK -NoOperation | OK -SearchSubtitles | OK -DownloadSubtitles | OK -SearchToMail | OK -TryUploadSubtitles | OK -UploadSubtitles | OK -SearchMoviesOnIMDB | OK -GetIMDBMovieDetails | OK -InsertMovie | OK -InsertMovieHash | OK -ServerInfo | OK -ReportWrongMovieHash | OK -ReportWrongImdbMovie | OK -SubtitlesVote | OK -AddComment | OK -AddRequest | OK -GetComments | OK -GetSubLanguages | OK -DetectLanguage | OK -GetAvailableTranslations | OK -GetTranslation | NOT WORK (Returns status of error 410 'Other or unknown error') -AutoUpdate | NOT WORK (Returns status: 'parse error. not well formed') -CheckMovieHash | OK -CheckMovieHash2 | OK -CheckSubHash | OK --------------------------------------------- diff --git a/OpenSubtitlesHandler/MovieHasher.cs b/OpenSubtitlesHandler/MovieHasher.cs deleted file mode 100644 index 25d91c1aca..0000000000 --- a/OpenSubtitlesHandler/MovieHasher.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.IO; -using System.Text; - -namespace OpenSubtitlesHandler -{ - public class MovieHasher - { - public static byte[] ComputeMovieHash(Stream input) - { - using (input) - { - long lhash, streamsize; - streamsize = input.Length; - lhash = streamsize; - - long i = 0; - byte[] buffer = new byte[sizeof(long)]; - while (i < 65536 / sizeof(long) && (input.Read(buffer, 0, sizeof(long)) > 0)) - { - i++; - lhash += BitConverter.ToInt64(buffer, 0); - } - - input.Position = Math.Max(0, streamsize - 65536); - i = 0; - while (i < 65536 / sizeof(long) && (input.Read(buffer, 0, sizeof(long)) > 0)) - { - i++; - lhash += BitConverter.ToInt64(buffer, 0); - } - byte[] result = BitConverter.GetBytes(lhash); - Array.Reverse(result); - return result; - } - } - - public static string ToHexadecimal(byte[] bytes) - { - var hexBuilder = new StringBuilder(); - for (int i = 0; i < bytes.Length; i++) - { - hexBuilder.Append(bytes[i].ToString("x2")); - } - return hexBuilder.ToString(); - } - } -} diff --git a/OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs b/OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs deleted file mode 100644 index 9535901375..0000000000 --- a/OpenSubtitlesHandler/Movies/CheckMovieHash2Data.cs +++ /dev/null @@ -1,42 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct CheckMovieHash2Data - { - private string _MovieHash; - private string _MovieImdbID; - private string _MovieName; - private string _MovieYear; - private string _MovieKind; - private string _SeriesSeason; - private string _SeriesEpisode; - private string _SeenCount; - - public string MovieHash { get { return _MovieHash; } set { _MovieHash = value; } } - public string MovieImdbID { get { return _MovieImdbID; } set { _MovieImdbID = value; } } - public string MovieName { get { return _MovieName; } set { _MovieName = value; } } - public string MovieYear { get { return _MovieYear; } set { _MovieYear = value; } } - public string MovieKind { get { return _MovieKind; } set { _MovieKind = value; } } - public string SeriesSeason { get { return _SeriesSeason; } set { _SeriesSeason = value; } } - public string SeriesEpisode { get { return _SeriesEpisode; } set { _SeriesEpisode = value; } } - public string SeenCount { get { return _SeenCount; } set { _SeenCount = value; } } - } -} diff --git a/OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs b/OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs deleted file mode 100644 index 96652fae74..0000000000 --- a/OpenSubtitlesHandler/Movies/CheckMovieHash2Result.cs +++ /dev/null @@ -1,37 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - public class CheckMovieHash2Result - { - private string name; - private List data = new List(); - - public string Name { get { return name; } set { name = value; } } - public List Items { get { return data; } set { data = value; } } - - public override string ToString() - { - return name; - } - } -} diff --git a/OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs b/OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs deleted file mode 100644 index 0d6c79f80b..0000000000 --- a/OpenSubtitlesHandler/Movies/CheckMovieHashResult.cs +++ /dev/null @@ -1,41 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct CheckMovieHashResult - { - private string name; - private string _MovieHash; - private string _MovieImdbID; - private string _MovieName; - private string _MovieYear; - - public string MovieHash { get { return _MovieHash; } set { _MovieHash = value; } } - public string MovieImdbID { get { return _MovieImdbID; } set { _MovieImdbID = value; } } - public string MovieName { get { return _MovieName; } set { _MovieName = value; } } - public string MovieYear { get { return _MovieYear; } set { _MovieYear = value; } } - public string Name { get { return name; } set { name = value; } } - - public override string ToString() - { - return name; - } - } -} diff --git a/OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs b/OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs deleted file mode 100644 index d0de7f8c64..0000000000 --- a/OpenSubtitlesHandler/Movies/InsertMovieHashParameters.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public class InsertMovieHashParameters - { - private string _moviehash = ""; - private string _moviebytesize = ""; - private string _imdbid = ""; - private string _movietimems = ""; - private string _moviefps = ""; - private string _moviefilename = ""; - - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public string moviebytesize { get { return _moviebytesize; } set { _moviebytesize = value; } } - public string imdbid { get { return _imdbid; } set { _imdbid = value; } } - public string movietimems { get { return _movietimems; } set { _movietimems = value; } } - public string moviefps { get { return _moviefps; } set { _moviefps = value; } } - public string moviefilename { get { return _moviefilename; } set { _moviefilename = value; } } - } -} diff --git a/OpenSubtitlesHandler/Movies/MovieSearchResult.cs b/OpenSubtitlesHandler/Movies/MovieSearchResult.cs deleted file mode 100644 index d771165830..0000000000 --- a/OpenSubtitlesHandler/Movies/MovieSearchResult.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct MovieSearchResult - { - private string id; - private string title; - - public string ID - { get { return id; } set { id = value; } } - public string Title - { get { return title; } set { title = value; } } - /// - /// Title - /// - /// - public override string ToString() - { - return title; - } - } -} diff --git a/OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs b/OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs deleted file mode 100644 index a0ecc87f8a..0000000000 --- a/OpenSubtitlesHandler/Movies/SearchToMailMovieParameter.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct SearchToMailMovieParameter - { - private string _moviehash; - private double _moviesize; - - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public double moviesize { get { return _moviesize; } set { _moviesize = value; } } - } -} diff --git a/OpenSubtitlesHandler/OpenSubtitles.cs b/OpenSubtitlesHandler/OpenSubtitles.cs deleted file mode 100644 index 76f70dc070..0000000000 --- a/OpenSubtitlesHandler/OpenSubtitles.cs +++ /dev/null @@ -1,2744 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using OpenSubtitlesHandler.Console; -using XmlRpcHandler; - -namespace OpenSubtitlesHandler -{ - /// - /// The core of the OpenSubtitles Handler library. All members are static. - /// - public sealed class OpenSubtitles - { - private static string XML_PRC_USERAGENT = ""; - // This is session id after log in, important value and MUST be set by LogIn before any other call. - private static string TOKEN = ""; - - /// - /// Set the useragent value. This must be called before doing anything else. - /// - /// The useragent value - public static void SetUserAgent(string agent) - { - XML_PRC_USERAGENT = agent; - } - - /*Session handling*/ - /// - /// Send a LogIn request, this must be called before anything else in this class. - /// - /// The user name of OpenSubtitles - /// The password - /// The language, usally en - /// Status of the login operation - public static IMethodResponse LogIn(string userName, string password, string language) - { - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(userName)); - parms.Add(new XmlRpcValueBasic(password)); - parms.Add(new XmlRpcValueBasic(language)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - var call = new XmlRpcMethodCall("LogIn", parms); - OSHConsole.WriteLine("Sending LogIn request to the server ...", DebugCode.Good); - - //File.WriteAllText(".\\request.txt", Encoding.UTF8.GetString(XmlRpcGenerator.Generate(call))); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - var re = new MethodResponseLogIn("Success", "Log in successful."); - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "token": re.Token = TOKEN = MEMBER.Data.Data.ToString(); OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": re.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "status": re.Status = MEMBER.Data.Data.ToString(); OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - return re; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Log in failed !"); - } - - public static async Task LogInAsync(string userName, string password, string language, CancellationToken cancellationToken) - { - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(userName)); - parms.Add(new XmlRpcValueBasic(password)); - parms.Add(new XmlRpcValueBasic(language)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - var call = new XmlRpcMethodCall("LogIn", parms); - OSHConsole.WriteLine("Sending LogIn request to the server ...", DebugCode.Good); - - //File.WriteAllText(".\\request.txt", Encoding.UTF8.GetString(XmlRpcGenerator.Generate(call))); - // Send the request to the server - var stream = await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken) - .ConfigureAwait(false); - - string response = Utilities.GetStreamString(stream); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - var re = new MethodResponseLogIn("Success", "Log in successful."); - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "token": - re.Token = TOKEN = MEMBER.Data.Data.ToString(); - OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - re.Seconds = double.Parse(MEMBER.Data.Data.ToString(), CultureInfo.InvariantCulture); - OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "status": - re.Status = MEMBER.Data.Data.ToString(); - OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - } - } - return re; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Log in failed !"); - } - - /// - /// Log out from the server. Call this to terminate the session. - /// - /// - public static IMethodResponse LogOut() - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("LogOut", parms); - - OSHConsole.WriteLine("Sending LogOut request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var strct = (XmlRpcValueStruct)calls[0].Parameters[0]; - OSHConsole.WriteLine("STATUS=" + ((XmlRpcValueBasic)strct.Members[0].Data).Data.ToString()); - OSHConsole.WriteLine("SECONDS=" + ((XmlRpcValueBasic)strct.Members[1].Data).Data.ToString()); - var re = new MethodResponseLogIn("Success", "Log out successful."); - re.Status = ((XmlRpcValueBasic)strct.Members[0].Data).Data.ToString(); - re.Seconds = (double)((XmlRpcValueBasic)strct.Members[1].Data).Data; - return re; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Log out failed !"); - } - /// - /// keep-alive user's session, verify token/session validity - /// - /// Status of the call operation - public static IMethodResponse NoOperation() - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("NoOperation", parms); - - OSHConsole.WriteLine("Sending NoOperation request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. We expect Struct here. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var R = new MethodResponseNoOperation(); - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data); break; - case "download_limits": - var dlStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dlmember in dlStruct.Members) - { - OSHConsole.WriteLine(" >" + dlmember.Name + "= " + dlmember.Data.Data.ToString()); - switch (dlmember.Name) - { - case "global_wrh_download_limit": R.global_wrh_download_limit = dlmember.Data.Data.ToString(); break; - case "client_ip": R.client_ip = dlmember.Data.Data.ToString(); break; - case "limit_check_by": R.limit_check_by = dlmember.Data.Data.ToString(); break; - case "client_24h_download_count": R.client_24h_download_count = dlmember.Data.Data.ToString(); break; - case "client_downlaod_quota": R.client_downlaod_quota = dlmember.Data.Data.ToString(); break; - case "client_24h_download_limit": R.client_24h_download_limit = dlmember.Data.Data.ToString(); break; - } - } - break; - } - } - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "NoOperation call failed !"); - } - /*Search and download*/ - /// - /// Search for subtitle files matching your videos using either video file hashes or IMDb IDs. - /// - /// List of search subtitle parameters which each one represents 'struct parameter' as descriped at http://trac.opensubtitles.org/projects/opensubtitles/wiki/XmlRpcSearchSubtitles - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitleSearch' - public static IMethodResponse SearchSubtitles(SubtitleSearchParameters[] parameters) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (parameters == null) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - if (parameters.Length == 0) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (SubtitleSearchParameters param in parameters) - { - var strct = new XmlRpcValueStruct(new List()); - // sublanguageid member - var member = new XmlRpcStructMember("sublanguageid", - new XmlRpcValueBasic(param.SubLangaugeID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - if (param.MovieHash.Length > 0 && param.MovieByteSize > 0) - { - member = new XmlRpcStructMember("moviehash", - new XmlRpcValueBasic(param.MovieHash, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - member = new XmlRpcStructMember("moviebytesize", - new XmlRpcValueBasic(param.MovieByteSize, XmlRpcBasicValueType.Int)); - strct.Members.Add(member); - } - if (param.Query.Length > 0) - { - member = new XmlRpcStructMember("query", - new XmlRpcValueBasic(param.Query, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - if (param.Episode.Length > 0 && param.Season.Length > 0) - { - member = new XmlRpcStructMember("season", - new XmlRpcValueBasic(param.Season, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - member = new XmlRpcStructMember("episode", - new XmlRpcValueBasic(param.Episode, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - // imdbid member - if (param.IMDbID.Length > 0) - { - member = new XmlRpcStructMember("imdbid", - new XmlRpcValueBasic(param.IMDbID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - // Add the struct to the array - array.Values.Add(strct); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("SearchSubtitles", parms); - OSHConsole.WriteLine("Sending SearchSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleSearch(); - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Search results: "); - - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break; - case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break; - case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break; - case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break; - case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break; - case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break; - case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break; - case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break; - case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break; - case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break; - case "MovieName": result.MovieName = submember.Data.Data.ToString(); break; - case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break; - case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break; - case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break; - case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break; - case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break; - case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break; - case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break; - case "SubBad": result.SubBad = submember.Data.Data.ToString(); break; - case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break; - case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break; - case "SeriesEpisode": result.SeriesEpisode = submember.Data.Data.ToString(); break; - case "SeriesSeason": result.SeriesSeason = submember.Data.Data.ToString(); break; - case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break; - case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break; - case "SubHash": result.SubHash = submember.Data.Data.ToString(); break; - case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break; - case "SubRating": result.SubRating = submember.Data.Data.ToString(); break; - case "SubSize": result.SubSize = submember.Data.Data.ToString(); break; - case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break; - case "UserID": result.UserID = submember.Data.Data.ToString(); break; - case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break; - case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Search Subtitles call failed !"); - } - - public static async Task SearchSubtitlesAsync(SubtitleSearchParameters[] parameters, CancellationToken cancellationToken) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (parameters == null) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - if (parameters.Length == 0) - { - OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle search parameter passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (SubtitleSearchParameters param in parameters) - { - var strct = new XmlRpcValueStruct(new List()); - // sublanguageid member - var member = new XmlRpcStructMember("sublanguageid", - new XmlRpcValueBasic(param.SubLangaugeID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - if (param.MovieHash.Length > 0 && param.MovieByteSize > 0) - { - member = new XmlRpcStructMember("moviehash", - new XmlRpcValueBasic(param.MovieHash, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - // moviehash member - member = new XmlRpcStructMember("moviebytesize", - new XmlRpcValueBasic(param.MovieByteSize, XmlRpcBasicValueType.Int)); - strct.Members.Add(member); - } - if (param.Query.Length > 0) - { - member = new XmlRpcStructMember("query", - new XmlRpcValueBasic(param.Query, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - if (param.Episode.Length > 0 && param.Season.Length > 0) - { - member = new XmlRpcStructMember("season", - new XmlRpcValueBasic(param.Season, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - member = new XmlRpcStructMember("episode", - new XmlRpcValueBasic(param.Episode, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - - // imdbid member - if (param.IMDbID.Length > 0) - { - member = new XmlRpcStructMember("imdbid", - new XmlRpcValueBasic(param.IMDbID, XmlRpcBasicValueType.String)); - strct.Members.Add(member); - } - // Add the struct to the array - array.Values.Add(strct); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("SearchSubtitles", parms); - OSHConsole.WriteLine("Sending SearchSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken).ConfigureAwait(false)); - - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleSearch(); - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Search results: "); - - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break; - case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break; - case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break; - case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break; - case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break; - case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break; - case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break; - case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break; - case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break; - case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break; - case "MovieName": result.MovieName = submember.Data.Data.ToString(); break; - case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break; - case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break; - case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break; - case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break; - case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break; - case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break; - case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break; - case "SubBad": result.SubBad = submember.Data.Data.ToString(); break; - case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break; - case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break; - case "SeriesEpisode": result.SeriesEpisode = submember.Data.Data.ToString(); break; - case "SeriesSeason": result.SeriesSeason = submember.Data.Data.ToString(); break; - case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break; - case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break; - case "SubHash": result.SubHash = submember.Data.Data.ToString(); break; - case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break; - case "SubRating": result.SubRating = submember.Data.Data.ToString(); break; - case "SubSize": result.SubSize = submember.Data.Data.ToString(); break; - case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break; - case "UserID": result.UserID = submember.Data.Data.ToString(); break; - case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break; - case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "Search Subtitles call failed !"); - } - - /// - /// Download subtitle file(s) - /// - /// The subtitle IDS (an array of IDSubtitleFile value that given by server as SearchSubtiles results) - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitleDownload' which will hold downloaded subtitles - public static IMethodResponse DownloadSubtitles(int[] subIDS) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (subIDS == null) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - if (subIDS.Length == 0) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (int id in subIDS) - { - array.Values.Add(new XmlRpcValueBasic(id, XmlRpcBasicValueType.Int)); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("DownloadSubtitles", parms); - OSHConsole.WriteLine("Sending DownloadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleDownload(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Download results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleDownloadResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "idsubtitlefile": result.IdSubtitleFile = (string)submember.Data.Data; break; - case "data": result.Data = (string)submember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine("> IDSubtilteFile= " + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "DownloadSubtitles call failed !"); - } - - public static async Task DownloadSubtitlesAsync(int[] subIDS, CancellationToken cancellationToken) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (subIDS == null) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - if (subIDS.Length == 0) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(); - foreach (int id in subIDS) - { - array.Values.Add(new XmlRpcValueBasic(id, XmlRpcBasicValueType.Int)); - } - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("DownloadSubtitles", parms); - OSHConsole.WriteLine("Sending DownloadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - - var httpResponse = await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken).ConfigureAwait(false); - - string response = Utilities.GetStreamString(httpResponse); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - // We expect Struct of 3 members: - //* the first is status - //* the second is [array of structs, each one includes subtitle file]. - //* the third is [double basic value] represent seconds token by server. - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitleDownload(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Download results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleDownloadResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "idsubtitlefile": result.IdSubtitleFile = (string)submember.Data.Data; break; - case "data": result.Data = (string)submember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine("> IDSubtilteFile= " + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "DownloadSubtitles call failed !"); - } - - /// - /// Returns comments for subtitles - /// - /// The subtitle IDS (an array of IDSubtitleFile value that given by server as SearchSubtiles results) - /// Status of the call operation. If the call success the response will be 'MethodResponseGetComments' - public static IMethodResponse GetComments(int[] subIDS) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - if (subIDS == null) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - if (subIDS.Length == 0) - { - OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error); - return new MethodResponseError("Fail", "No subtitle id passed"); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN)); - // Add subtitle search parameters. Each one will be like 'array' of structs. - var array = new XmlRpcValueArray(subIDS); - // Add the array to the parameters - parms.Add(array); - // Call ! - var call = new XmlRpcMethodCall("GetComments", parms); - OSHConsole.WriteLine("Sending GetComments request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetComments(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Comments results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue commentStruct in rarray.Values) - { - if (commentStruct == null) continue; - if (!(commentStruct is XmlRpcValueStruct)) continue; - - var result = new GetCommentsResult(); - foreach (XmlRpcStructMember commentmember in ((XmlRpcValueStruct)commentStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (commentmember.Name) - { - case "IDSubtitle": result.IDSubtitle = (string)commentmember.Data.Data; break; - case "UserID": result.UserID = (string)commentmember.Data.Data; break; - case "UserNickName": result.UserNickName = (string)commentmember.Data.Data; break; - case "Comment": result.Comment = (string)commentmember.Data.Data; break; - case "Created": result.Created = (string)commentmember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine("> IDSubtitle= " + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetComments call failed !"); - } - - /// - /// Schedule a periodical search for subtitles matching given video files, send results to user's e-mail address. - /// - /// The language 3 lenght ids array - /// The movies parameters - /// Status of the call operation. If the call success the response will be 'MethodResponseSearchToMail' - public static IMethodResponse SearchToMail(string[] languageIDS, SearchToMailMovieParameter[] movies) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Array of sub langs - var a = new XmlRpcValueArray(languageIDS); - parms.Add(a); - // Array of video parameters - a = new XmlRpcValueArray(); - foreach (SearchToMailMovieParameter p in movies) - { - var str = new XmlRpcValueStruct(new List()); - str.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(p.moviehash))); - str.Members.Add(new XmlRpcStructMember("moviesize", new XmlRpcValueBasic(p.moviesize))); - a.Values.Add(str); - } - parms.Add(a); - var call = new XmlRpcMethodCall("SearchToMail", parms); - - OSHConsole.WriteLine("Sending SearchToMail request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSearchToMail(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "SearchToMail call failed !"); - } - /*Movies*/ - /// - /// Search for a movie (using movie title) - /// - /// Movie title user is searching for, this is cleaned-up a bit (remove dvdrip, etc.) before searching - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitleSearch' - public static IMethodResponse SearchMoviesOnIMDB(string query) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add query param - parms.Add(new XmlRpcValueBasic(query, XmlRpcBasicValueType.String)); - // Call ! - var call = new XmlRpcMethodCall("SearchMoviesOnIMDB", parms); - OSHConsole.WriteLine("Sending SearchMoviesOnIMDB request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseMovieSearch(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Search results:"); - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new MovieSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "id": result.ID = (string)submember.Data.Data; break; - case "title": result.Title = (string)submember.Data.Data; break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "SearchMoviesOnIMDB call failed !"); - } - /// - /// Get movie details for given IMDb ID - /// - /// http://www.imdb.com/ - /// Status of the call operation. If the call success the response will be 'MethodResponseMovieDetails' - public static IMethodResponse GetIMDBMovieDetails(string imdbid) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN)); - // Add query param - parms.Add(new XmlRpcValueBasic(imdbid)); - // Call ! - var call = new XmlRpcMethodCall("GetIMDBMovieDetails", parms); - OSHConsole.WriteLine("Sending GetIMDBMovieDetails request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseMovieDetails(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "data") - { - // We expect struct with details... - if (MEMBER.Data is XmlRpcValueStruct) - { - OSHConsole.WriteLine("Details result:"); - var detailsStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dmem in detailsStruct.Members) - { - switch (dmem.Name) - { - case "id": R.ID = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "title": R.Title = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "year": R.Year = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "cover": R.CoverLink = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "duration": R.Duration = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "tagline": R.Tagline = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "plot": R.Plot = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "goofs": R.Goofs = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "trivia": R.Trivia = dmem.Data.Data.ToString(); OSHConsole.WriteLine(">" + dmem.Name + "= " + dmem.Data.Data.ToString()); break; - case "cast": - // this is another struct with cast members... - OSHConsole.WriteLine(">" + dmem.Name + "= "); - var castStruct = (XmlRpcValueStruct)dmem.Data; - foreach (XmlRpcStructMember castMemeber in castStruct.Members) - { - R.Cast.Add(castMemeber.Data.Data.ToString()); - OSHConsole.WriteLine(" >" + castMemeber.Data.Data.ToString()); - } - break; - case "directors": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is another struct with directors members... - var directorsStruct = (XmlRpcValueStruct)dmem.Data; - foreach (XmlRpcStructMember directorsMember in directorsStruct.Members) - { - R.Directors.Add(directorsMember.Data.Data.ToString()); - OSHConsole.WriteLine(" >" + directorsMember.Data.Data.ToString()); - } - break; - case "writers": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is another struct with writers members... - var writersStruct = (XmlRpcValueStruct)dmem.Data; - foreach (XmlRpcStructMember writersMember in writersStruct.Members) - { - R.Writers.Add(writersMember.Data.Data.ToString()); - OSHConsole.WriteLine("+->" + writersMember.Data.Data.ToString()); - } - break; - case "awards": - // this is an array of genres... - var awardsArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic award in awardsArray.Values) - { - R.Awards.Add(award.Data.ToString()); - OSHConsole.WriteLine(" >" + award.Data.ToString()); - } - break; - case "genres": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of genres... - var genresArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic genre in genresArray.Values) - { - R.Genres.Add(genre.Data.ToString()); - OSHConsole.WriteLine(" >" + genre.Data.ToString()); - } - break; - case "country": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of country... - var countryArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic country in countryArray.Values) - { - R.Country.Add(country.Data.ToString()); - OSHConsole.WriteLine(" >" + country.Data.ToString()); - } - break; - case "language": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of language... - var languageArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic language in languageArray.Values) - { - R.Language.Add(language.Data.ToString()); - OSHConsole.WriteLine(" >" + language.Data.ToString()); - } - break; - case "certification": - OSHConsole.WriteLine(">" + dmem.Name + "= "); - // this is an array of certification... - var certificationArray = (XmlRpcValueArray)dmem.Data; - foreach (XmlRpcValueBasic certification in certificationArray.Values) - { - R.Certification.Add(certification.Data.ToString()); - OSHConsole.WriteLine(" >" + certification.Data.ToString()); - } - break; - } - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetIMDBMovieDetails call failed !"); - } - /// - /// Allows registered users to insert new movies (not stored in IMDb) to the database. - /// - /// Movie title - /// Release year - /// Status of the call operation. If the call success the response will be 'MethodResponseInsertMovie' - public static IMethodResponse InsertMovie(string movieName, string movieyear) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - // Add token param - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // Add movieinfo struct - var movieinfo = new XmlRpcValueStruct(new List()); - movieinfo.Members.Add(new XmlRpcStructMember("moviename", new XmlRpcValueBasic(movieName))); - movieinfo.Members.Add(new XmlRpcStructMember("movieyear", new XmlRpcValueBasic(movieyear))); - parms.Add(movieinfo); - // Call ! - var call = new XmlRpcMethodCall("InsertMovie", parms); - OSHConsole.WriteLine("Sending InsertMovie request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseInsertMovie(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - if (MEMBER.Name == "status") - { - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("Status= " + R.Status); - } - else if (MEMBER.Name == "seconds") - { - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine("Seconds= " + R.Seconds); - } - else if (MEMBER.Name == "id") - { - R.ID = (string)MEMBER.Data.Data; - OSHConsole.WriteLine("ID= " + R.Seconds); - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "InsertMovie call failed !"); - } - /// - /// Inserts or updates data to tables, which are used for CheckMovieHash() and !CheckMovieHash2(). - /// - /// The parameters - /// Status of the call operation. If the call success the response will be 'MethodResponseInsertMovieHash' - public static IMethodResponse InsertMovieHash(InsertMovieHashParameters[] parameters) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - foreach (InsertMovieHashParameters p in parameters) - { - var pstruct = new XmlRpcValueStruct(new List()); - pstruct.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(p.moviehash))); - pstruct.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(p.moviebytesize))); - pstruct.Members.Add(new XmlRpcStructMember("imdbid", new XmlRpcValueBasic(p.imdbid))); - pstruct.Members.Add(new XmlRpcStructMember("movietimems", new XmlRpcValueBasic(p.movietimems))); - pstruct.Members.Add(new XmlRpcStructMember("moviefps", new XmlRpcValueBasic(p.moviefps))); - pstruct.Members.Add(new XmlRpcStructMember("moviefilename", new XmlRpcValueBasic(p.moviefilename))); - parms.Add(pstruct); - } - var call = new XmlRpcMethodCall("InsertMovieHash", parms); - - OSHConsole.WriteLine("Sending InsertMovieHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseInsertMovieHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - switch (dataMember.Name) - { - case "accepted_moviehashes": - var mh = (XmlRpcValueArray)dataMember.Data; - foreach (IXmlRpcValue val in mh.Values) - { - if (val is XmlRpcValueBasic) - { - R.accepted_moviehashes.Add(val.Data.ToString()); - } - } - break; - case "new_imdbs": - var mi = (XmlRpcValueArray)dataMember.Data; - foreach (IXmlRpcValue val in mi.Values) - { - if (val is XmlRpcValueBasic) - { - R.new_imdbs.Add(val.Data.ToString()); - } - } - break; - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "InsertMovieHash call failed !"); - } - /*Reporting and rating*/ - /// - /// Get basic server information and statistics - /// - /// Status of the call operation. If the call success the response will be 'MethodResponseServerInfo' - public static IMethodResponse ServerInfo() - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("ServerInfo", parms); - - OSHConsole.WriteLine("Sending ServerInfo request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseServerInfo(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "xmlrpc_version": - R.xmlrpc_version = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "xmlrpc_url": - R.xmlrpc_url = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "application": - R.application = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "contact": - R.contact = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "website_url": - R.website_url = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_online_total": - R.users_online_total = (int)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_online_program": - R.users_online_program = (int)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_loggedin": - R.users_loggedin = (int)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_max_alltime": - R.users_max_alltime = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "users_registered": - R.users_registered = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "subs_downloads": - R.subs_downloads = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "subs_subtitle_files": - R.subs_subtitle_files = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "movies_total": - R.movies_total = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "movies_aka": - R.movies_aka = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "total_subtitles_languages": - R.total_subtitles_languages = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "last_update_strings": - //R.total_subtitles_languages = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + ":"); - var luStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember luMemeber in luStruct.Members) - { - R.last_update_strings.Add(luMemeber.Name + " [" + luMemeber.Data.Data.ToString() + "]"); - OSHConsole.WriteLine(" >" + luMemeber.Name + "= " + luMemeber.Data.Data.ToString()); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "ServerInfo call failed !"); - } - /// - /// Report wrong subtitle file <--> video file combination - /// - /// Identifier of the subtitle file <--> video file combination - /// Status of the call operation. If the call success the response will be 'MethodResponseReportWrongMovieHash' - public static IMethodResponse ReportWrongMovieHash(string IDSubMovieFile) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - parms.Add(new XmlRpcValueBasic(IDSubMovieFile, XmlRpcBasicValueType.String)); - var call = new XmlRpcMethodCall("ReportWrongMovieHash", parms); - - OSHConsole.WriteLine("Sending ReportWrongMovieHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseReportWrongMovieHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": - R.Status = (string)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - case "seconds": - R.Seconds = (double)MEMBER.Data.Data; - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "ReportWrongMovieHash call failed !"); - } - /// - /// This method is needed to report bad movie hash for imdbid. This method should be used for correcting wrong entries, - /// when using CheckMovieHash2. Pass moviehash and moviebytesize for file, and imdbid as new, corrected one IMDBID - /// (id number without trailing zeroes). After some reports, moviehash will be linked to new imdbid. - /// - /// The movie hash - /// The movie size in bytes - /// The movie imbid - /// Status of the call operation. If the call success the response will be 'MethodResponseReportWrongImdbMovie' - public static IMethodResponse ReportWrongImdbMovie(string moviehash, string moviebytesize, string imdbid) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(moviehash))); - s.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(moviebytesize))); - s.Members.Add(new XmlRpcStructMember("imdbid", new XmlRpcValueBasic(imdbid))); - parms.Add(s); - var call = new XmlRpcMethodCall("ReportWrongImdbMovie", parms); - - OSHConsole.WriteLine("Sending ReportWrongImdbMovie request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAddComment(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "ReportWrongImdbMovie call failed !"); - } - /// - /// Rate subtitles - /// - /// Id of subtitle (NOT subtitle file) user wants to rate - /// Subtitle rating, must be in interval 1 (worst) to 10 (best). - /// Status of the call operation. If the call success the response will be 'MethodResponseSubtitlesVote' - public static IMethodResponse SubtitlesVote(int idsubtitle, int score) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("idsubtitle", new XmlRpcValueBasic(idsubtitle))); - s.Members.Add(new XmlRpcStructMember("score", new XmlRpcValueBasic(score))); - parms.Add(s); - var call = new XmlRpcMethodCall("SubtitlesVote", parms); - - OSHConsole.WriteLine("Sending SubtitlesVote request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseSubtitlesVote(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMemeber in dataStruct.Members) - { - OSHConsole.WriteLine(" >" + dataMemeber.Name + "= " + dataMemeber.Data.Data.ToString()); - switch (dataMemeber.Name) - { - case "SubRating": R.SubRating = dataMemeber.Data.Data.ToString(); break; - case "SubSumVotes": R.SubSumVotes = dataMemeber.Data.Data.ToString(); break; - case "IDSubtitle": R.IDSubtitle = dataMemeber.Data.Data.ToString(); break; - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "SubtitlesVote call failed !"); - } - /// - /// Add comment to a subtitle - /// - /// Subtitle identifier (BEWARE! this is not the ID of subtitle file but of the whole subtitle (a subtitle can contain multiple subtitle files)) - /// User's comment - /// Optional parameter. If set to 1, subtitles are marked as bad. - /// Status of the call operation. If the call success the response will be 'MethodResponseAddComment' - public static IMethodResponse AddComment(int idsubtitle, string comment, int badsubtitle) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("idsubtitle", new XmlRpcValueBasic(idsubtitle))); - s.Members.Add(new XmlRpcStructMember("comment", new XmlRpcValueBasic(comment))); - s.Members.Add(new XmlRpcStructMember("badsubtitle", new XmlRpcValueBasic(badsubtitle))); - parms.Add(s); - var call = new XmlRpcMethodCall("AddComment", parms); - - OSHConsole.WriteLine("Sending AddComment request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAddComment(); - - // To make sure response is not currepted by server, do it in loop - foreach (var MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "AddComment call failed !"); - } - /// - /// Add new request for subtitles, user must be logged in - /// - /// The subtitle language id 3 length - /// http://www.imdb.com/ - /// The comment - /// Status of the call operation. If the call success the response will be 'MethodResponseAddRequest' - public static IMethodResponse AddRequest(string sublanguageid, string idmovieimdb, string comment) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - s.Members.Add(new XmlRpcStructMember("sublanguageid", new XmlRpcValueBasic(sublanguageid))); - s.Members.Add(new XmlRpcStructMember("idmovieimdb", new XmlRpcValueBasic(idmovieimdb))); - s.Members.Add(new XmlRpcStructMember("comment", new XmlRpcValueBasic(comment))); - parms.Add(s); - var call = new XmlRpcMethodCall("AddRequest", parms); - - OSHConsole.WriteLine("Sending AddRequest request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAddRequest(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMemeber in dataStruct.Members) - { - switch (dataMemeber.Name) - { - case "request_url": R.request_url = dataMemeber.Data.Data.ToString(); OSHConsole.WriteLine(">" + dataMemeber.Name + "= " + dataMemeber.Data.Data.ToString()); break; - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "AddRequest call failed !"); - } - /*User interface*/ - /// - /// Get list of supported subtitle languages - /// - /// ISO639-1 2-letter language code of user's interface language. - /// Status of the call operation. If the call success the response will be 'MethodResponseGetSubLanguages' - public static IMethodResponse GetSubLanguages(string language) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(language)); - var call = new XmlRpcMethodCall("GetSubLanguages", parms); - - OSHConsole.WriteLine("Sending GetSubLanguages request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetSubLanguages(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data":// array of structs - var array = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue value in array.Values) - { - if (value is XmlRpcValueStruct) - { - var valueStruct = (XmlRpcValueStruct)value; - var lang = new SubtitleLanguage(); - OSHConsole.WriteLine(">SubLanguage:"); - foreach (XmlRpcStructMember langMemeber in valueStruct.Members) - { - OSHConsole.WriteLine(" >" + langMemeber.Name + "= " + langMemeber.Data.Data.ToString()); - switch (langMemeber.Name) - { - case "SubLanguageID": lang.SubLanguageID = langMemeber.Data.Data.ToString(); break; - case "LanguageName": lang.LanguageName = langMemeber.Data.Data.ToString(); break; - case "ISO639": lang.ISO639 = langMemeber.Data.Data.ToString(); break; - } - } - R.Languages.Add(lang); - } - else - { - OSHConsole.WriteLine(">" + MEMBER.Name + "= " + - MEMBER.Data.Data.ToString() + " [Struct expected !]", DebugCode.Warning); - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetSubLanguages call failed !"); - } - /// - /// Detect language for given strings - /// - /// Array of strings you want language detected for - /// The encoding that will be used to get buffer of given strings. (this is not OS official parameter) - /// Status of the call operation. If the call success the response will be 'MethodResponseDetectLanguage' - public static IMethodResponse DetectLanguage(string[] texts, Encoding encodingUsed) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - // We need to gzip texts then code them with base 24 - var decodedTexts = new List(); - foreach (string text in texts) - { - // compress - Stream str = new MemoryStream(); - byte[] stringData = encodingUsed.GetBytes(text); - str.Write(stringData, 0, stringData.Length); - str.Position = 0; - byte[] data = Utilities.Compress(str); - //base 64 - decodedTexts.Add(Convert.ToBase64String(data)); - } - parms.Add(new XmlRpcValueArray(decodedTexts.ToArray())); - var call = new XmlRpcMethodCall("DetectLanguage", parms); - - OSHConsole.WriteLine("Sending DetectLanguage request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseDetectLanguage(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - if (MEMBER.Data is XmlRpcValueStruct) - { - OSHConsole.WriteLine(">Languages:"); - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - var lang = new DetectLanguageResult(); - lang.InputSample = dataMember.Name; - lang.LanguageID = dataMember.Data.Data.ToString(); - R.Results.Add(lang); - OSHConsole.WriteLine(" >" + dataMember.Name + " (" + dataMember.Data.Data.ToString() + ")"); - } - } - else - { - OSHConsole.WriteLine(">Languages ?? Struct expected but server return another type!!", DebugCode.Warning); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "DetectLanguage call failed !"); - } - /// - /// Get available translations for given program - /// - /// Name of the program/client application you want translations for. Currently supported values: subdownloader, oscar - /// Status of the call operation. If the call success the response will be 'MethodResponseGetAvailableTranslations' - public static IMethodResponse GetAvailableTranslations(string program) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(program)); - var call = new XmlRpcMethodCall("GetAvailableTranslations", parms); - - OSHConsole.WriteLine("Sending GetAvailableTranslations request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetAvailableTranslations(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - OSHConsole.WriteLine(">data:"); - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - if (dataMember.Data is XmlRpcValueStruct) - { - var resStruct = (XmlRpcValueStruct)dataMember.Data; - var res = new GetAvailableTranslationsResult(); - res.LanguageID = dataMember.Name; - OSHConsole.WriteLine(" >LanguageID: " + dataMember.Name); - foreach (XmlRpcStructMember resMember in resStruct.Members) - { - switch (resMember.Name) - { - case "LastCreated": res.LastCreated = resMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + resMember.Name + "= " + resMember.Data.Data.ToString()); break; - case "StringsNo": res.StringsNo = resMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + resMember.Name + "= " + resMember.Data.Data.ToString()); break; - } - R.Results.Add(res); - } - } - else - { - OSHConsole.WriteLine(" >Struct expected !!", DebugCode.Warning); - } - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetAvailableTranslations call failed !"); - } - /// - /// Get a translation for given program and language - /// - /// language ​ISO639-1 2-letter code - /// available formats: [gnugettext compatible: mo, po] and [additional: txt, xml] - /// Name of the program/client application you want translations for. (currently supported values: subdownloader, oscar) - /// Status of the call operation. If the call success the response will be 'MethodResponseGetTranslation' - public static IMethodResponse GetTranslation(string iso639, string format, string program) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(iso639)); - parms.Add(new XmlRpcValueBasic(format)); - parms.Add(new XmlRpcValueBasic(program)); - var call = new XmlRpcMethodCall("GetTranslation", parms); - - OSHConsole.WriteLine("Sending GetTranslation request to the server ...", DebugCode.Good); - // Send the request to the server - //File.WriteAllText(".\\REQUEST_GetTranslation.xml", Encoding.ASCII.GetString(XmlRpcGenerator.Generate(call))); - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseGetTranslation(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": R.ContentData = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "GetTranslation call failed !"); - } - /// - /// Check for the latest version of given application - /// - /// name of the program/client application you want to check. (Currently supported values: subdownloader, oscar) - /// Status of the call operation. If the call success the response will be 'MethodResponseAutoUpdate' - public static IMethodResponse AutoUpdate(string program) - { - /*if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - }*/ - // Method call .. - var parms = new List(); - // parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueBasic(program)); - // parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - - var call = new XmlRpcMethodCall("AutoUpdate", parms); - OSHConsole.WriteLine("Sending AutoUpdate request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseAutoUpdate(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "version": R.version = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "url_windows": R.url_windows = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "url_linux": R.url_linux = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "comments": R.comments = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "AutoUpdate call failed !"); - } - /*Checking*/ - /// - /// Check if video file hashes are already stored in the database - /// - /// Array of video file hashes - /// Status of the call operation. If the call success the response will be 'MethodResponseCheckMovieHash' - public static IMethodResponse CheckMovieHash(string[] hashes) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueArray(hashes)); - var call = new XmlRpcMethodCall("CheckMovieHash", parms); - - OSHConsole.WriteLine("Sending CheckMovieHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseCheckMovieHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - OSHConsole.WriteLine(">Data:"); - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - var res = new CheckMovieHashResult(); - res.Name = dataMember.Name; - OSHConsole.WriteLine(" >" + res.Name + ":"); - var movieStruct = (XmlRpcValueStruct)dataMember.Data; - foreach (XmlRpcStructMember movieMember in movieStruct.Members) - { - switch (movieMember.Name) - { - case "MovieHash": res.MovieHash = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieImdbID": res.MovieImdbID = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieName": res.MovieName = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieYear": res.MovieYear = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - } - } - R.Results.Add(res); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "CheckMovieHash call failed !"); - } - /// - /// Check if video file hashes are already stored in the database. This method returns matching !MovieImdbID, MovieName, MovieYear, SeriesSeason, SeriesEpisode, - /// MovieKind if available for each $moviehash, always sorted by SeenCount DESC. - /// - /// Array of video file hashes - /// Status of the call operation. If the call success the response will be 'MethodResponseCheckMovieHash2' - public static IMethodResponse CheckMovieHash2(string[] hashes) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueArray(hashes)); - var call = new XmlRpcMethodCall("CheckMovieHash2", parms); - - OSHConsole.WriteLine("Sending CheckMovieHash2 request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseCheckMovieHash2(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - OSHConsole.WriteLine(">Data:"); - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - var res = new CheckMovieHash2Result(); - res.Name = dataMember.Name; - OSHConsole.WriteLine(" >" + res.Name + ":"); - - var dataArray = (XmlRpcValueArray)dataMember.Data; - foreach (XmlRpcValueStruct movieStruct in dataArray.Values) - { - var d = new CheckMovieHash2Data(); - foreach (XmlRpcStructMember movieMember in movieStruct.Members) - { - switch (movieMember.Name) - { - case "MovieHash": d.MovieHash = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieImdbID": d.MovieImdbID = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieName": d.MovieName = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieYear": d.MovieYear = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "MovieKind": d.MovieKind = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "SeriesSeason": d.SeriesSeason = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "SeriesEpisode": d.SeriesEpisode = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - case "SeenCount": d.MovieYear = movieMember.Data.Data.ToString(); OSHConsole.WriteLine(" >" + movieMember.Name + "= " + movieMember.Data.Data.ToString()); break; - } - } - res.Items.Add(d); - } - R.Results.Add(res); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "CheckMovieHash2 call failed !"); - } - /// - /// Check if given subtitle files are already stored in the database - /// - /// Array of subtitle file hashes (MD5 hashes of subtitle file contents) - /// Status of the call operation. If the call success the response will be 'MethodResponseCheckSubHash' - public static IMethodResponse CheckSubHash(string[] hashes) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - parms.Add(new XmlRpcValueArray(hashes)); - var call = new XmlRpcMethodCall("CheckSubHash", parms); - - OSHConsole.WriteLine("Sending CheckSubHash request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseCheckSubHash(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - OSHConsole.WriteLine(">Data:"); - var dataStruct = (XmlRpcValueStruct)MEMBER.Data; - foreach (XmlRpcStructMember dataMember in dataStruct.Members) - { - OSHConsole.WriteLine(" >" + dataMember.Name + "= " + dataMember.Data.Data.ToString()); - var r = new CheckSubHashResult(); - r.Hash = dataMember.Name; - r.SubID = dataMember.Data.Data.ToString(); - R.Results.Add(r); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "CheckSubHash call failed !"); - } - /*Upload*/ - /// - /// Try to upload subtitles, perform pre-upload checking (i.e. check if subtitles already exist on server) - /// - /// The subtitle parameters collection to try to upload - /// Status of the call operation. If the call success the response will be 'MethodResponseTryUploadSubtitles' - public static IMethodResponse TryUploadSubtitles(TryUploadSubtitlesParameters[] subs) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String)); - var s = new XmlRpcValueStruct(new List()); - int i = 1; - foreach (var cd in subs) - { - var member = new XmlRpcStructMember("cd" + i, null); - var memberStruct = new XmlRpcValueStruct(new List()); - memberStruct.Members.Add(new XmlRpcStructMember("subhash", new XmlRpcValueBasic(cd.subhash))); - memberStruct.Members.Add(new XmlRpcStructMember("subfilename", new XmlRpcValueBasic(cd.subfilename))); - memberStruct.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(cd.moviehash))); - memberStruct.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(cd.moviebytesize))); - memberStruct.Members.Add(new XmlRpcStructMember("moviefps", new XmlRpcValueBasic(cd.moviefps))); - memberStruct.Members.Add(new XmlRpcStructMember("movietimems", new XmlRpcValueBasic(cd.movietimems))); - memberStruct.Members.Add(new XmlRpcStructMember("movieframes", new XmlRpcValueBasic(cd.movieframes))); - memberStruct.Members.Add(new XmlRpcStructMember("moviefilename", new XmlRpcValueBasic(cd.moviefilename))); - member.Data = memberStruct; - s.Members.Add(member); - i++; - } - parms.Add(s); - var call = new XmlRpcMethodCall("TryUploadSubtitles", parms); - - OSHConsole.WriteLine("Sending TryUploadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseTryUploadSubtitles(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "alreadyindb": R.AlreadyInDB = (int)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": - if (MEMBER.Data is XmlRpcValueArray) - { - OSHConsole.WriteLine("Results: "); - - var rarray = (XmlRpcValueArray)MEMBER.Data; - foreach (IXmlRpcValue subStruct in rarray.Values) - { - if (subStruct == null) continue; - if (!(subStruct is XmlRpcValueStruct)) continue; - - var result = new SubtitleSearchResult(); - foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members) - { - // To avoid errors of arranged info or missing ones, let's do it with switch.. - switch (submember.Name) - { - case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break; - case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break; - case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break; - case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break; - case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break; - case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break; - case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break; - case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break; - case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break; - case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break; - case "MovieName": result.MovieName = submember.Data.Data.ToString(); break; - case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break; - case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break; - case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break; - case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break; - case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break; - case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break; - case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break; - case "SubBad": result.SubBad = submember.Data.Data.ToString(); break; - case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break; - case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break; - case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break; - case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break; - case "SubHash": result.SubHash = submember.Data.Data.ToString(); break; - case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break; - case "SubRating": result.SubRating = submember.Data.Data.ToString(); break; - case "SubSize": result.SubSize = submember.Data.Data.ToString(); break; - case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break; - case "UserID": result.UserID = submember.Data.Data.ToString(); break; - case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break; - case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break; - } - } - R.Results.Add(result); - OSHConsole.WriteLine(">" + result.ToString()); - } - } - else// Unknown data ? - { - OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning); - } - break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "TryUploadSubtitles call failed !"); - } - /// - /// Upload given subtitles to OSDb server - /// - /// The pamaters of upload method - /// Status of the call operation. If the call success the response will be 'MethodResponseUploadSubtitles' - public static IMethodResponse UploadSubtitles(UploadSubtitleInfoParameters info) - { - if (TOKEN == "") - { - OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error); - return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first."); - } - // Method call .. - var parms = new List(); - parms.Add(new XmlRpcValueBasic(TOKEN)); - // Main struct - var s = new XmlRpcValueStruct(new List()); - - // Base info member as struct - var member = new XmlRpcStructMember("baseinfo", null); - var memberStruct = new XmlRpcValueStruct(new List()); - memberStruct.Members.Add(new XmlRpcStructMember("idmovieimdb", new XmlRpcValueBasic(info.idmovieimdb))); - memberStruct.Members.Add(new XmlRpcStructMember("sublanguageid", new XmlRpcValueBasic(info.sublanguageid))); - memberStruct.Members.Add(new XmlRpcStructMember("moviereleasename", new XmlRpcValueBasic(info.moviereleasename))); - memberStruct.Members.Add(new XmlRpcStructMember("movieaka", new XmlRpcValueBasic(info.movieaka))); - memberStruct.Members.Add(new XmlRpcStructMember("subauthorcomment", new XmlRpcValueBasic(info.subauthorcomment))); - // memberStruct.Members.Add(new XmlRpcStructMember("hearingimpaired", new XmlRpcValueBasic(info.hearingimpaired))); - // memberStruct.Members.Add(new XmlRpcStructMember("highdefinition", new XmlRpcValueBasic(info.highdefinition))); - // memberStruct.Members.Add(new XmlRpcStructMember("automatictranslation", new XmlRpcValueBasic(info.automatictranslation))); - member.Data = memberStruct; - s.Members.Add(member); - - // CDS members - int i = 1; - foreach (UploadSubtitleParameters cd in info.CDS) - { - var member2 = new XmlRpcStructMember("cd" + i, null); - var memberStruct2 = new XmlRpcValueStruct(new List()); - memberStruct2.Members.Add(new XmlRpcStructMember("subhash", new XmlRpcValueBasic(cd.subhash))); - memberStruct2.Members.Add(new XmlRpcStructMember("subfilename", new XmlRpcValueBasic(cd.subfilename))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviehash", new XmlRpcValueBasic(cd.moviehash))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviebytesize", new XmlRpcValueBasic(cd.moviebytesize))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviefps", new XmlRpcValueBasic(cd.moviefps))); - memberStruct2.Members.Add(new XmlRpcStructMember("movietimems", new XmlRpcValueBasic(cd.movietimems))); - memberStruct2.Members.Add(new XmlRpcStructMember("movieframes", new XmlRpcValueBasic(cd.movieframes))); - memberStruct2.Members.Add(new XmlRpcStructMember("moviefilename", new XmlRpcValueBasic(cd.moviefilename))); - memberStruct2.Members.Add(new XmlRpcStructMember("subcontent", new XmlRpcValueBasic(cd.subcontent))); - member2.Data = memberStruct2; - s.Members.Add(member2); - i++; - } - - // add main struct to parameters - parms.Add(s); - // add user agent - //parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT)); - var call = new XmlRpcMethodCall("UploadSubtitles", parms); - OSHConsole.WriteLine("Sending UploadSubtitles request to the server ...", DebugCode.Good); - // Send the request to the server - string response = Utilities.GetStreamString(Utilities.SendRequest(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT)); - if (!response.Contains("ERROR:")) - { - // No error occur, get and decode the response. - XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response); - if (calls.Length > 0) - { - if (calls[0].Parameters.Count > 0) - { - var mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0]; - // Create the response, we'll need it later - var R = new MethodResponseUploadSubtitles(); - - // To make sure response is not currepted by server, do it in loop - foreach (XmlRpcStructMember MEMBER in mainStruct.Members) - { - switch (MEMBER.Name) - { - case "status": R.Status = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "seconds": R.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "data": R.Data = (string)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - case "subtitles": R.SubTitles = (bool)MEMBER.Data.Data; OSHConsole.WriteLine(">" + MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break; - } - } - // Return the response to user !! - return R; - } - } - } - else - { - OSHConsole.WriteLine(response, DebugCode.Error); - return new MethodResponseError("Fail", response); - } - return new MethodResponseError("Fail", "UploadSubtitles call failed !"); - } - } -} diff --git a/OpenSubtitlesHandler/OpenSubtitlesHandler.csproj b/OpenSubtitlesHandler/OpenSubtitlesHandler.csproj deleted file mode 100644 index eabd3e070c..0000000000 --- a/OpenSubtitlesHandler/OpenSubtitlesHandler.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - netstandard2.0 - false - - - diff --git a/OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs b/OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs deleted file mode 100644 index 39d0485459..0000000000 --- a/OpenSubtitlesHandler/OtherTypes/GetAvailableTranslationsResult.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - public struct GetAvailableTranslationsResult - { - private string _language; - private string _LastCreated; - private string _StringsNo; - - public string LanguageID { get { return _language; } set { _language = value; } } - public string LastCreated { get { return _LastCreated; } set { _LastCreated = value; } } - public string StringsNo { get { return _StringsNo; } set { _StringsNo = value; } } - /// - /// LanguageID (LastCreated) - /// - /// - public override string ToString() - { - return _language + " (" + _LastCreated + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs b/OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs deleted file mode 100644 index 8f4aa9db42..0000000000 --- a/OpenSubtitlesHandler/OtherTypes/GetCommentsResult.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct GetCommentsResult - { - private string _IDSubtitle; - private string _UserID; - private string _UserNickName; - private string _Comment; - private string _Created; - - public string IDSubtitle { get { return _IDSubtitle; } set { _IDSubtitle = value; } } - public string UserID { get { return _UserID; } set { _UserID = value; } } - public string UserNickName { get { return _UserNickName; } set { _UserNickName = value; } } - public string Comment { get { return _Comment; } set { _Comment = value; } } - public string Created { get { return _Created; } set { _Created = value; } } - } -} diff --git a/OpenSubtitlesHandler/Properties/AssemblyInfo.cs b/OpenSubtitlesHandler/Properties/AssemblyInfo.cs deleted file mode 100644 index b5ae23021c..0000000000 --- a/OpenSubtitlesHandler/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using System.Resources; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenSubtitlesHandler")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] -[assembly: AssemblyCopyright("Copyright © 2013 Ala Ibrahim Hadid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -[assembly: AssemblyVersion("1.0.3.0")] -[assembly: AssemblyFileVersion("2019.1.20.3")] diff --git a/OpenSubtitlesHandler/Readme.txt b/OpenSubtitlesHandler/Readme.txt deleted file mode 100644 index d5814aec16..0000000000 --- a/OpenSubtitlesHandler/Readme.txt +++ /dev/null @@ -1,20 +0,0 @@ -OpenSubtitlesHandler -==================== -This project is for OpenSubtitles.org integration‏. The point is to allow user to access OpenSubtitles.org database directly -within ASM without the need to open internet browser. -The plan: Implement the "OSDb protocol" http://trac.opensubtitles.org/projects/opensubtitles/wiki/OSDb - -Copyright: -========= -This library ann all its content are written by Ala Ibrahim Hadid. -Copyright © Ala Ibrahim Hadid 2013 -mailto:ahdsoftwares@hotmail.com - -Resources: -========== -* GetHash.dll: this dll is used to compute hash for movie. - For more information please visit http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes#C2 - -XML_RPC: -======== -This class is created to generate XML-RPC requests as XML String. All you need is to call XML_RPC.Generate() method. diff --git a/OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs b/OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs deleted file mode 100644 index a2fbe87736..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/CheckSubHashResult.cs +++ /dev/null @@ -1,30 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct CheckSubHashResult - { - private string _hash; - private string _id; - - public string Hash { get { return _hash; } set { _hash = value; } } - public string SubID { get { return _id; } set { _id = value; } } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs deleted file mode 100644 index e4194994c3..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleDownloadResult.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct SubtitleDownloadResult - { - private string idsubtitlefile; - private string data; - - public string IdSubtitleFile - { get { return idsubtitlefile; } set { idsubtitlefile = value; } } - /// - /// Get or set the data of subtitle file. To decode, decode the string to base64 and then decompress with GZIP. - /// - public string Data - { get { return data; } set { data = value; } } - /// - /// IdSubtitleFile - /// - /// - public override string ToString() - { - return idsubtitlefile.ToString(); - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs deleted file mode 100644 index b2dc154133..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleLanguage.cs +++ /dev/null @@ -1,39 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace OpenSubtitlesHandler -{ - public struct SubtitleLanguage - { - private string _SubLanguageID; - private string _LanguageName; - private string _ISO639; - - public string SubLanguageID { get { return _SubLanguageID; } set { _SubLanguageID = value; } } - public string LanguageName { get { return _LanguageName; } set { _LanguageName = value; } } - public string ISO639 { get { return _ISO639; } set { _ISO639 = value; } } - /// - /// LanguageName [SubLanguageID] - /// - /// LanguageName [SubLanguageID] - public override string ToString() - { - return _LanguageName + " [" + _SubLanguageID + "]"; - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs deleted file mode 100644 index 5c8f8c01a4..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchParameters.cs +++ /dev/null @@ -1,82 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// Paramaters for subtitle search call - /// - public struct SubtitleSearchParameters - { - public SubtitleSearchParameters(string subLanguageId, string query = "", string season = "", string episode = "", string movieHash = "", long movieByteSize = 0, string imdbid = "") - { - this.subLanguageId = subLanguageId; - this.movieHash = movieHash; - this.movieByteSize = movieByteSize; - this.imdbid = imdbid; - this._episode = episode; - this._season = season; - this._query = query; - } - - private string subLanguageId; - private string movieHash; - private long movieByteSize; - private string imdbid; - private string _query; - private string _episode; - - public string Episode - { - get { return _episode; } - set { _episode = value; } - } - - public string Season - { - get { return _season; } - set { _season = value; } - } - - private string _season; - - public string Query - { - get { return _query; } - set { _query = value; } - } - - /// - /// List of language ISO639-3 language codes to search for, divided by ',' (e.g. 'cze,eng,slo') - /// - public string SubLangaugeID { get { return subLanguageId; } set { subLanguageId = value; } } - /// - /// Video file hash as calculated by one of the implementation functions as seen on http://trac.opensubtitles.org/projects/opensubtitles/wiki/HashSourceCodes - /// - public string MovieHash { get { return movieHash; } set { movieHash = value; } } - /// - /// Size of video file in bytes - /// - public long MovieByteSize { get { return movieByteSize; } set { movieByteSize = value; } } - /// - /// ​IMDb ID of movie this video is part of, belongs to. - /// - public string IMDbID { get { return imdbid; } set { imdbid = value; } } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs b/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs deleted file mode 100644 index a4a8dd3e69..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/SubtitleSearchResult.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - /// - /// The subtitle search result that comes with server response on SearchSubtitles successed call - /// - public struct SubtitleSearchResult - { - private string _IDSubMovieFile; - private string _MovieHash; - private string _MovieByteSize; - private string _MovieTimeMS; - private string _IDSubtitleFile; - private string _SubFileName; - private string _SubActualCD; - private string _SubSize; - private string _SubHash; - private string _IDSubtitle; - private string _UserID; - private string _SubLanguageID; - private string _SubFormat; - private string _SeriesSeason; - private string _SeriesEpisode; - private string _SubSumCD; - private string _SubAuthorComment; - private string _SubAddDate; - private string _SubBad; - private string _SubRating; - private string _SubDownloadsCnt; - private string _MovieReleaseName; - private string _IDMovie; - private string _IDMovieImdb; - private string _MovieName; - private string _MovieNameEng; - private string _MovieYear; - private string _MovieImdbRating; - private string _UserNickName; - private string _ISO639; - private string _LanguageName; - private string _SubDownloadLink; - private string _ZipDownloadLink; - - public string IDSubMovieFile - { get { return _IDSubMovieFile; } set { _IDSubMovieFile = value; } } - public string MovieHash - { get { return _MovieHash; } set { _MovieHash = value; } } - public string MovieByteSize - { get { return _MovieByteSize; } set { _MovieByteSize = value; } } - public string MovieTimeMS - { get { return _MovieTimeMS; } set { _MovieTimeMS = value; } } - public string IDSubtitleFile - { get { return _IDSubtitleFile; } set { _IDSubtitleFile = value; } } - public string SubFileName - { get { return _SubFileName; } set { _SubFileName = value; } } - public string SubActualCD - { get { return _SubActualCD; } set { _SubActualCD = value; } } - public string SubSize - { get { return _SubSize; } set { _SubSize = value; } } - public string SubHash - { get { return _SubHash; } set { _SubHash = value; } } - public string IDSubtitle - { get { return _IDSubtitle; } set { _IDSubtitle = value; } } - public string UserID - { get { return _UserID; } set { _UserID = value; } } - public string SubLanguageID - { get { return _SubLanguageID; } set { _SubLanguageID = value; } } - public string SubFormat - { get { return _SubFormat; } set { _SubFormat = value; } } - public string SubSumCD - { get { return _SubSumCD; } set { _SubSumCD = value; } } - public string SubAuthorComment - { get { return _SubAuthorComment; } set { _SubAuthorComment = value; } } - public string SubAddDate - { get { return _SubAddDate; } set { _SubAddDate = value; } } - public string SubBad - { get { return _SubBad; } set { _SubBad = value; } } - public string SubRating - { get { return _SubRating; } set { _SubRating = value; } } - public string SubDownloadsCnt - { get { return _SubDownloadsCnt; } set { _SubDownloadsCnt = value; } } - public string MovieReleaseName - { get { return _MovieReleaseName; } set { _MovieReleaseName = value; } } - public string IDMovie - { get { return _IDMovie; } set { _IDMovie = value; } } - public string IDMovieImdb - { get { return _IDMovieImdb; } set { _IDMovieImdb = value; } } - public string MovieName - { get { return _MovieName; } set { _MovieName = value; } } - public string MovieNameEng - { get { return _MovieNameEng; } set { _MovieNameEng = value; } } - public string MovieYear - { get { return _MovieYear; } set { _MovieYear = value; } } - public string MovieImdbRating - { get { return _MovieImdbRating; } set { _MovieImdbRating = value; } } - public string UserNickName - { get { return _UserNickName; } set { _UserNickName = value; } } - public string ISO639 - { get { return _ISO639; } set { _ISO639 = value; } } - public string LanguageName - { get { return _LanguageName; } set { _LanguageName = value; } } - public string SubDownloadLink - { get { return _SubDownloadLink; } set { _SubDownloadLink = value; } } - public string ZipDownloadLink - { get { return _ZipDownloadLink; } set { _ZipDownloadLink = value; } } - public string SeriesSeason - { get { return _SeriesSeason; } set { _SeriesSeason = value; } } - public string SeriesEpisode - { get { return _SeriesEpisode; } set { _SeriesEpisode = value; } } - /// - /// SubFileName + " (" + SubFormat + ")" - /// - /// - public override string ToString() - { - return _SubFileName + " (" + _SubFormat + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs b/OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs deleted file mode 100644 index 11f3a706c1..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/TryUploadSubtitlesParameters.cs +++ /dev/null @@ -1,47 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public class TryUploadSubtitlesParameters - { - private string _subhash = ""; - private string _subfilename = ""; - private string _moviehash = ""; - private string _moviebytesize = ""; - private int _movietimems = 0; - private int _movieframes = 0; - private double _moviefps = 0; - private string _moviefilename = ""; - - public string subhash { get { return _subhash; } set { _subhash = value; } } - public string subfilename { get { return _subfilename; } set { _subfilename = value; } } - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public string moviebytesize { get { return _moviebytesize; } set { _moviebytesize = value; } } - public int movietimems { get { return _movietimems; } set { _movietimems = value; } } - public int movieframes { get { return _movieframes; } set { _movieframes = value; } } - public double moviefps { get { return _moviefps; } set { _moviefps = value; } } - public string moviefilename { get { return _moviefilename; } set { _moviefilename = value; } } - - public override string ToString() - { - return _subfilename + " (" + _subhash + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs b/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs deleted file mode 100644 index 133cc1d238..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleInfoParameter.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace OpenSubtitlesHandler -{ - public class UploadSubtitleInfoParameters - { - private string _idmovieimdb; - private string _moviereleasename; - private string _movieaka; - private string _sublanguageid; - private string _subauthorcomment; - private bool _hearingimpaired; - private bool _highdefinition; - private bool _automatictranslation; - private List cds; - - public string idmovieimdb { get { return _idmovieimdb; } set { _idmovieimdb = value; } } - public string moviereleasename { get { return _moviereleasename; } set { _moviereleasename = value; } } - public string movieaka { get { return _movieaka; } set { _movieaka = value; } } - public string sublanguageid { get { return _sublanguageid; } set { _sublanguageid = value; } } - public string subauthorcomment { get { return _subauthorcomment; } set { _subauthorcomment = value; } } - public bool hearingimpaired { get { return _hearingimpaired; } set { _hearingimpaired = value; } } - public bool highdefinition { get { return _highdefinition; } set { _highdefinition = value; } } - public bool automatictranslation { get { return _automatictranslation; } set { _automatictranslation = value; } } - public List CDS { get { return cds; } set { cds = value; } } - } -} diff --git a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs b/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs deleted file mode 100644 index 9910dadc56..0000000000 --- a/OpenSubtitlesHandler/SubtitleTypes/UploadSubtitleParameters.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace OpenSubtitlesHandler -{ - public struct UploadSubtitleParameters - { - private string _subhash; - private string _subfilename; - private string _moviehash; - private double _moviebytesize; - private int _movietimems; - private int _movieframes; - private double _moviefps; - private string _moviefilename; - private string _subcontent; - - public string subhash { get { return _subhash; } set { _subhash = value; } } - public string subfilename { get { return _subfilename; } set { _subfilename = value; } } - public string moviehash { get { return _moviehash; } set { _moviehash = value; } } - public double moviebytesize { get { return _moviebytesize; } set { _moviebytesize = value; } } - public int movietimems { get { return _movietimems; } set { _movietimems = value; } } - public int movieframes { get { return _movieframes; } set { _movieframes = value; } } - public double moviefps { get { return _moviefps; } set { _moviefps = value; } } - public string moviefilename { get { return _moviefilename; } set { _moviefilename = value; } } - /// - /// Sub content. Note: this value must be the subtitle file gziped and then base64 decoded. - /// - public string subcontent { get { return _subcontent; } set { _subcontent = value; } } - - public override string ToString() - { - return _subfilename + " (" + _subhash + ")"; - } - } -} diff --git a/OpenSubtitlesHandler/Utilities.cs b/OpenSubtitlesHandler/Utilities.cs deleted file mode 100644 index 429e28a8f4..0000000000 --- a/OpenSubtitlesHandler/Utilities.cs +++ /dev/null @@ -1,174 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Net; -using MediaBrowser.Model.Cryptography; - -namespace OpenSubtitlesHandler -{ - /// - /// Include helper methods. All member are statics. - /// - public sealed class Utilities - { - public static ICryptoProvider CryptographyProvider { get; set; } - public static IHttpClient HttpClient { get; set; } - private static string XML_RPC_SERVER = "https://api.opensubtitles.org/xml-rpc"; - //private static string XML_RPC_SERVER = "https://92.240.234.122/xml-rpc"; - private static string HostHeader = "api.opensubtitles.org:443"; - - /// - /// Compute movie hash - /// - /// The hash as Hexadecimal string - public static string ComputeHash(Stream stream) - { - byte[] hash = MovieHasher.ComputeMovieHash(stream); - return MovieHasher.ToHexadecimal(hash); - } - /// - /// Decompress data using GZip - /// - /// The stream that hold the data - /// Bytes array of decompressed data - public static byte[] Decompress(Stream dataToDecompress) - { - using (var target = new MemoryStream()) - { - using (var decompressionStream = new System.IO.Compression.GZipStream(dataToDecompress, System.IO.Compression.CompressionMode.Decompress)) - { - decompressionStream.CopyTo(target); - } - return target.ToArray(); - } - } - - /// - /// Compress data using GZip (the retunred buffer will be WITHOUT HEADER) - /// - /// The stream that hold the data - /// Bytes array of compressed data WITHOUT HEADER bytes - public static byte[] Compress(Stream dataToCompress) - { - /*using (var compressed = new MemoryStream()) - { - using (var compressor = new System.IO.Compression.GZipStream(compressed, - System.IO.Compression.CompressionMode.Compress)) - { - dataToCompress.CopyTo(compressor); - } - // Get the compressed bytes only after closing the GZipStream - return compressed.ToArray(); - }*/ - //using (var compressedOutput = new MemoryStream()) - //{ - // using (var compressedStream = new ZlibStream(compressedOutput, - // Ionic.Zlib.CompressionMode.Compress, - // CompressionLevel.Default, false)) - // { - // var buffer = new byte[4096]; - // int byteCount; - // do - // { - // byteCount = dataToCompress.Read(buffer, 0, buffer.Length); - - // if (byteCount > 0) - // { - // compressedStream.Write(buffer, 0, byteCount); - // } - // } while (byteCount > 0); - // } - // return compressedOutput.ToArray(); - //} - - throw new NotImplementedException(); - } - - /// - /// Handle server response stream and decode it as given encoding string. - /// - /// The string of the stream after decode using given encoding - public static string GetStreamString(Stream responseStream) - { - using (responseStream) - { - // Handle response, should be XML text. - var data = new List(); - while (true) - { - int r = responseStream.ReadByte(); - if (r < 0) - break; - data.Add((byte)r); - } - var bytes = data.ToArray(); - return Encoding.ASCII.GetString(bytes, 0, bytes.Length); - } - } - - public static byte[] GetASCIIBytes(string text) - { - return Encoding.ASCII.GetBytes(text); - } - - /// - /// Send a request to the server - /// - /// The request buffer to send as bytes array. - /// The user agent value. - /// Response of the server or stream of error message as string started with 'ERROR:' keyword. - public static Stream SendRequest(byte[] request, string userAgent) - { - return SendRequestAsync(request, userAgent, CancellationToken.None).Result; - } - - public static async Task SendRequestAsync(byte[] request, string userAgent, CancellationToken cancellationToken) - { - var options = new HttpRequestOptions - { - RequestContentBytes = request, - RequestContentType = "text/xml", - UserAgent = userAgent, - Host = HostHeader, - Url = XML_RPC_SERVER, - - // Response parsing will fail with this enabled - EnableHttpCompression = false, - - CancellationToken = cancellationToken, - BufferContent = false - }; - - if (string.IsNullOrEmpty(options.UserAgent)) - { - options.UserAgent = "xmlrpc-epi-php/0.2 (PHP)"; - } - - var result = await HttpClient.Post(options).ConfigureAwait(false); - - return result.Content; - } - - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt b/OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt deleted file mode 100644 index a4de38cdea..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Docs/XML-RPC.txt +++ /dev/null @@ -1,225 +0,0 @@ -XML-RPC Specification - -Tue, Jun 15, 1999; by Dave Winer. - -Updated 6/30/03 DW - -Updated 10/16/99 DW - -Updated 1/21/99 DW - -This specification documents the XML-RPC protocol implemented in UserLand Frontier 5.1. - -For a non-technical explanation, see XML-RPC for Newbies. - -This page provides all the information that an implementor needs. - -Overview - -XML-RPC is a Remote Procedure Calling protocol that works over the Internet. - -An XML-RPC message is an HTTP-POST request. The body of the request is in XML. A procedure executes on the server and the value it returns is also formatted in XML. - -Procedure parameters can be scalars, numbers, strings, dates, etc.; and can also be complex record and list structures. - -Request example - -Here's an example of an XML-RPC request: - -POST /RPC2 HTTP/1.0 -User-Agent: Frontier/5.1.2 (WinNT) -Host: betty.userland.com -Content-Type: text/xml -Content-length: 181 - - - - examples.getStateName - - - - 41 - - - - - -Header requirements - -The format of the URI in the first line of the header is not specified. For example, it could be empty, a single slash, if the server is only handling XML-RPC calls. However, if the server is handling a mix of incoming HTTP requests, we allow the URI to help route the request to the code that handles XML-RPC requests. (In the example, the URI is /RPC2, telling the server to route the request to the "RPC2" responder.) - -A User-Agent and Host must be specified. - -The Content-Type is text/xml. - -The Content-Length must be specified and must be correct. - -Payload format - -The payload is in XML, a single structure. - -The must contain a sub-item, a string, containing the name of the method to be called. The string may only contain identifier characters, upper and lower-case A-Z, the numeric characters, 0-9, underscore, dot, colon and slash. It's entirely up to the server to decide how to interpret the characters in a methodName. - -For example, the methodName could be the name of a file containing a script that executes on an incoming request. It could be the name of a cell in a database table. Or it could be a path to a file contained within a hierarchy of folders and files. - -If the procedure call has parameters, the must contain a sub-item. The sub-item can contain any number of s, each of which has a . - -Scalar s - -s can be scalars, type is indicated by nesting the value inside one of the tags listed in this table: - -Tag Type Example - or four-byte signed integer -12 - 0 (false) or 1 (true) 1 - string hello world - double-precision signed floating point number -12.214 - date/time 19980717T14:08:55 - base64-encoded binary eW91IGNhbid0IHJlYWQgdGhpcyE= - -If no type is indicated, the type is string. - -s - -A value can also be of type . - -A contains s and each contains a and a . - -Here's an example of a two-element : - - - - lowerBound - 18 - - - upperBound - 139 - - - -s can be recursive, any may contain a or any other type, including an , described below. - -s - -A value can also be of type . - -An contains a single element, which can contain any number of s. - -Here's an example of a four-element array: - - - - 12 - Egypt - 0 - -31 - - - - elements do not have names. - -You can mix types as the example above illustrates. - -s can be recursive, any value may contain an or any other type, including a , described above. - -Response example - -Here's an example of a response to an XML-RPC request: - -HTTP/1.1 200 OK -Connection: close -Content-Length: 158 -Content-Type: text/xml -Date: Fri, 17 Jul 1998 19:55:08 GMT -Server: UserLand Frontier/5.1.2-WinNT - - South Dakota - -Response format - -Unless there's a lower-level error, always return 200 OK. - -The Content-Type is text/xml. Content-Length must be present and correct. - -The body of the response is a single XML structure, a , which can contain a single which contains a single which contains a single . - -The could also contain a which contains a which is a containing two elements, one named , an and one named , a . - -A can not contain both a and a . - -Fault example - -HTTP/1.1 200 OK -Connection: close -Content-Length: 426 -Content-Type: text/xml -Date: Fri, 17 Jul 1998 19:55:02 GMT -Server: UserLand Frontier/5.1.2-WinNT - - faultCode 4 faultString Too many parameters. - -Strategies/Goals - -Firewalls. The goal of this protocol is to lay a compatible foundation across different environments, no new power is provided beyond the capabilities of the CGI interface. Firewall software can watch for POSTs whose Content-Type is text/xml. - -Discoverability. We wanted a clean, extensible format that's very simple. It should be possible for an HTML coder to be able to look at a file containing an XML-RPC procedure call, understand what it's doing, and be able to modify it and have it work on the first or second try. - -Easy to implement. We also wanted it to be an easy to implement protocol that could quickly be adapted to run in other environments or on other operating systems. - -Updated 1/21/99 DW - -The following questions came up on the UserLand discussion group as XML-RPC was being implemented in Python. - - The Response Format section says "The body of the response is a single XML structure, a , which can contain a single ..." This is confusing. Can we leave out the ? - - No you cannot leave it out if the procedure executed successfully. There are only two options, either a response contains a structure or it contains a structure. That's why we used the word "can" in that sentence. - - Is "boolean" a distinct data type, or can boolean values be interchanged with integers (e.g. zero=false, non-zero=true)? - - Yes, boolean is a distinct data type. Some languages/environments allow for an easy coercion from zero to false and one to true, but if you mean true, send a boolean type with the value true, so your intent can't possibly be misunderstood. - - What is the legal syntax (and range) for integers? How to deal with leading zeros? Is a leading plus sign allowed? How to deal with whitespace? - - An integer is a 32-bit signed number. You can include a plus or minus at the beginning of a string of numeric characters. Leading zeros are collapsed. Whitespace is not permitted. Just numeric characters preceeded by a plus or minus. - - What is the legal syntax (and range) for floating point values (doubles)? How is the exponent represented? How to deal with whitespace? Can infinity and "not a number" be represented? - - There is no representation for infinity or negative infinity or "not a number". At this time, only decimal point notation is allowed, a plus or a minus, followed by any number of numeric characters, followed by a period and any number of numeric characters. Whitespace is not allowed. The range of allowable values is implementation-dependent, is not specified. - - What characters are allowed in strings? Non-printable characters? Null characters? Can a "string" be used to hold an arbitrary chunk of binary data? - - Any characters are allowed in a string except < and &, which are encoded as < and &. A string can be used to encode binary data. - - Does the "struct" element keep the order of keys. Or in other words, is the struct "foo=1, bar=2" equivalent to "bar=2, foo=1" or not? - - The struct element does not preserve the order of the keys. The two structs are equivalent. - - Can the struct contain other members than and ? Is there a global list of faultCodes? (so they can be mapped to distinct exceptions for languages like Python and Java)? - - A struct may not contain members other than those specified. This is true for all other structures. We believe the specification is flexible enough so that all reasonable data-transfer needs can be accomodated within the specified structures. If you believe strongly that this is not true, please post a message on the discussion group. - - There is no global list of fault codes. It is up to the server implementer, or higher-level standards to specify fault codes. - - What timezone should be assumed for the dateTime.iso8601 type? UTC? localtime? - - Don't assume a timezone. It should be specified by the server in its documentation what assumptions it makes about timezones. - -Additions - - type. 1/21/99 DW. - -Updated 6/30/03 DW - -Removed "ASCII" from definition of string. - -Changed copyright dates, below, to 1999-2003 from 1998-99. - -Copyright and disclaimer - -© Copyright 1998-2003 UserLand Software. All Rights Reserved. - -This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and these paragraphs are included on all such copies and derivative works. - -This document may not be modified in any way, such as by removing the copyright notice or references to UserLand or other organizations. Further, while these copyright restrictions apply to the written XML-RPC specification, no claim of ownership is made by UserLand to the protocol it describes. Any party may, for commercial or non-commercial purposes, implement this protocol without royalty or license fee to UserLand. The limited permissions granted herein are perpetual and will not be revoked by UserLand or its successors or assigns. - -This document and the information contained herein is provided on an "AS IS" basis and USERLAND DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. diff --git a/OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs b/OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs deleted file mode 100644 index 51e0ee98f8..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Enums/XmlRpcValueType.cs +++ /dev/null @@ -1,25 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -namespace XmlRpcHandler -{ - public enum XmlRpcBasicValueType - { - String, Int, Boolean, Double, dateTime_iso8601, base64 - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs b/OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs deleted file mode 100644 index 7cc1a164f6..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Types/XmlRpcMethodCall.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace XmlRpcHandler -{ - /// - /// A method call - /// - public struct XmlRpcMethodCall - { - /// - /// A method call - /// - /// The name of this method - public XmlRpcMethodCall(string name) - { - this.name = name; - this.parameters = new List(); - } - /// - /// A method call - /// - /// The name of this method - /// A list of parameters - public XmlRpcMethodCall(string name, List parameters) - { - this.name = name; - this.parameters = parameters; - } - - private string name; - private List parameters; - - /// - /// Get or set the name of this method - /// - public string Name - { get { return name; } set { name = value; } } - /// - /// Get or set the parameters to be sent - /// - public List Parameters - { get { return parameters; } set { parameters = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs b/OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs deleted file mode 100644 index c918790cba..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/IXmlRpcValue.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace XmlRpcHandler -{ - public abstract class IXmlRpcValue - { - public IXmlRpcValue() - { } - public IXmlRpcValue(object data) - { - this.data = data; - } - private object data; - /// - /// Get or set the data of this value - /// - public virtual object Data { get { return data; } set { data = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs deleted file mode 100644 index 2aad7ebffe..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcStructMember.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -namespace XmlRpcHandler -{ - public class XmlRpcStructMember - { - public XmlRpcStructMember(string name, IXmlRpcValue data) - { - this.name = name; - this.data = data; - } - private string name; - private IXmlRpcValue data; - - /// - /// Get or set the name of this member - /// - public string Name - { get { return name; } set { name = value; } } - /// - /// Get or set the data of this member - /// - public IXmlRpcValue Data - { get { return data; } set { data = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs deleted file mode 100644 index d10a80175b..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueArray.cs +++ /dev/null @@ -1,121 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; - -namespace XmlRpcHandler -{ - public class XmlRpcValueArray : IXmlRpcValue - { - public XmlRpcValueArray() : - base() - { - values = new List(); - } - public XmlRpcValueArray(object data) : - base(data) - { - values = new List(); - } - public XmlRpcValueArray(string[] texts) : - base() - { - values = new List(); - foreach (string val in texts) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(int[] ints) : - base() - { - values = new List(); - foreach (int val in ints) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(double[] doubles) : - base() - { - values = new List(); - foreach (double val in doubles) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(bool[] bools) : - base() - { - values = new List(); - foreach (bool val in bools) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(long[] base24s) : - base() - { - values = new List(); - foreach (long val in base24s) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(DateTime[] dates) : - base() - { - values = new List(); - foreach (var val in dates) - { - values.Add(new XmlRpcValueBasic(val)); - } - } - public XmlRpcValueArray(XmlRpcValueBasic[] basicValues) : - base() - { - values = new List(); - foreach (var val in basicValues) - { - values.Add(val); - } - } - public XmlRpcValueArray(XmlRpcValueStruct[] structs) : - base() - { - values = new List(); - foreach (var val in structs) - { - values.Add(val); - } - } - public XmlRpcValueArray(XmlRpcValueArray[] arrays) : - base() - { - values = new List(); - foreach (var val in arrays) - { - values.Add(val); - } - } - private List values; - - public List Values { get { return values; } set { values = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs deleted file mode 100644 index f2811b9889..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueBasic.cs +++ /dev/null @@ -1,99 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; - -namespace XmlRpcHandler -{ - public class XmlRpcValueBasic : IXmlRpcValue - { - public XmlRpcValueBasic() - : base() - { } - public XmlRpcValueBasic(string data) - : base(data) - { this.type = XmlRpcBasicValueType.String; } - public XmlRpcValueBasic(int data) - : base(data) - { this.type = XmlRpcBasicValueType.Int; } - public XmlRpcValueBasic(double data) - : base(data) - { this.type = XmlRpcBasicValueType.Double; } - public XmlRpcValueBasic(DateTime data) - : base(data) - { this.type = XmlRpcBasicValueType.dateTime_iso8601; } - public XmlRpcValueBasic(bool data) - : base(data) - { this.type = XmlRpcBasicValueType.Boolean; } - public XmlRpcValueBasic(long data) - : base(data) - { this.type = XmlRpcBasicValueType.base64; } - public XmlRpcValueBasic(object data, XmlRpcBasicValueType type) - : base(data) - { this.type = type; } - - private XmlRpcBasicValueType type = XmlRpcBasicValueType.String; - /// - /// Get or set the type of this basic value - /// - public XmlRpcBasicValueType ValueType { get { return type; } set { type = value; } } - /*Oprators. help a lot.*/ - public static implicit operator string(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return f.Data.ToString(); - else - throw new Exception("Unable to convert, this value is not string type."); - } - public static implicit operator int(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (int)f.Data; - else - throw new Exception("Unable to convert, this value is not int type."); - } - public static implicit operator double(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (double)f.Data; - else - throw new Exception("Unable to convert, this value is not double type."); - } - public static implicit operator bool(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (bool)f.Data; - else - throw new Exception("Unable to convert, this value is not bool type."); - } - public static implicit operator long(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (long)f.Data; - else - throw new Exception("Unable to convert, this value is not long type."); - } - public static implicit operator DateTime(XmlRpcValueBasic f) - { - if (f.type == XmlRpcBasicValueType.String) - return (DateTime)f.Data; - else - throw new Exception("Unable to convert, this value is not DateTime type."); - } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs b/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs deleted file mode 100644 index 4863e38e84..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/Values/XmlRpcValueStruct.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -using System.Collections.Generic; - -namespace XmlRpcHandler -{ - /// - /// Represents XML-RPC struct - /// - public class XmlRpcValueStruct : IXmlRpcValue - { - /// - /// Represents XML-RPC struct - /// - /// - public XmlRpcValueStruct(List members) - { this.members = members; } - - private List members; - /// - /// Get or set the members collection - /// - public List Members - { get { return members; } set { members = value; } } - } -} diff --git a/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs b/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs deleted file mode 100644 index a79a278fa1..0000000000 --- a/OpenSubtitlesHandler/XML-RPC/XmlRpcGenerator.cs +++ /dev/null @@ -1,350 +0,0 @@ -/* This file is part of OpenSubtitles Handler - A library that handle OpenSubtitles.org XML-RPC methods. - - Copyright © Ala Ibrahim Hadid 2013 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Text; -using System.Xml; -using OpenSubtitlesHandler; - -namespace XmlRpcHandler -{ - /// - /// A class for making XML-RPC requests. The requests then can be sent directly as http request. - /// - public class XmlRpcGenerator - { - /// - /// Generate XML-RPC request using method call. - /// - /// The method call - /// The request in xml. - public static byte[] Generate(XmlRpcMethodCall method) - { return Generate(new XmlRpcMethodCall[] { method }); } - /// - /// Generate XML-RPC request using method calls array. - /// - /// The method calls array - /// The request in utf8 xml string as buffer - public static byte[] Generate(XmlRpcMethodCall[] methods) - { - if (methods == null) - throw new Exception("No method to write !"); - if (methods.Length == 0) - throw new Exception("No method to write !"); - // Create xml - var sett = new XmlWriterSettings(); - sett.Indent = true; - - sett.Encoding = Encoding.UTF8; - - using (var ms = new MemoryStream()) - { - using (var XMLwrt = XmlWriter.Create(ms, sett)) - { - // Let's write the methods - foreach (XmlRpcMethodCall method in methods) - { - XMLwrt.WriteStartElement("methodCall");//methodCall - XMLwrt.WriteStartElement("methodName");//methodName - XMLwrt.WriteString(method.Name); - XMLwrt.WriteEndElement();//methodName - XMLwrt.WriteStartElement("params");//params - // Write values - foreach (IXmlRpcValue p in method.Parameters) - { - XMLwrt.WriteStartElement("param");//param - if (p is XmlRpcValueBasic) - { - WriteBasicValue(XMLwrt, (XmlRpcValueBasic)p); - } - else if (p is XmlRpcValueStruct) - { - WriteStructValue(XMLwrt, (XmlRpcValueStruct)p); - } - else if (p is XmlRpcValueArray) - { - WriteArrayValue(XMLwrt, (XmlRpcValueArray)p); - } - XMLwrt.WriteEndElement();//param - } - - XMLwrt.WriteEndElement();//params - XMLwrt.WriteEndElement();//methodCall - } - XMLwrt.Flush(); - return ms.ToArray(); - } - } - } - /// - /// Decode response then return the values - /// - /// The response xml string as provided by server as methodResponse - /// - public static XmlRpcMethodCall[] DecodeMethodResponse(string xmlResponse) - { - var methods = new List(); - var sett = new XmlReaderSettings(); - sett.DtdProcessing = DtdProcessing.Ignore; - sett.IgnoreWhitespace = true; - MemoryStream str; - if (xmlResponse.Contains(@"encoding=""utf-8""")) - { - str = new MemoryStream(Encoding.UTF8.GetBytes(xmlResponse)); - } - else - { - str = new MemoryStream(Utilities.GetASCIIBytes(xmlResponse)); - } - using (str) - { - using (var XMLread = XmlReader.Create(str, sett)) - { - var call = new XmlRpcMethodCall("methodResponse"); - // Read parameters - while (XMLread.Read()) - { - if (XMLread.Name == "param" && XMLread.IsStartElement()) - { - IXmlRpcValue val = ReadValue(XMLread); - if (val != null) - call.Parameters.Add(val); - } - } - methods.Add(call); - return methods.ToArray(); - } - } - } - - private static void WriteBasicValue(XmlWriter XMLwrt, XmlRpcValueBasic val) - { - XMLwrt.WriteStartElement("value");//value - switch (val.ValueType) - { - case XmlRpcBasicValueType.String: - XMLwrt.WriteStartElement("string"); - if (val.Data != null) - XMLwrt.WriteString(val.Data.ToString()); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.Int: - XMLwrt.WriteStartElement("int"); - if (val.Data != null) - XMLwrt.WriteString(val.Data.ToString()); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.Boolean: - XMLwrt.WriteStartElement("boolean"); - if (val.Data != null) - XMLwrt.WriteString(((bool)val.Data) ? "1" : "0"); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.Double: - XMLwrt.WriteStartElement("double"); - if (val.Data != null) - XMLwrt.WriteString(val.Data.ToString()); - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.dateTime_iso8601: - XMLwrt.WriteStartElement("dateTime.iso8601"); - // Get date time format - if (val.Data != null) - { - var time = (DateTime)val.Data; - string dt = time.Year + time.Month.ToString("D2") + time.Day.ToString("D2") + - "T" + time.Hour.ToString("D2") + ":" + time.Minute.ToString("D2") + ":" + - time.Second.ToString("D2"); - XMLwrt.WriteString(dt); - } - XMLwrt.WriteEndElement(); - break; - case XmlRpcBasicValueType.base64: - XMLwrt.WriteStartElement("base64"); - if (val.Data != null) - XMLwrt.WriteString(Convert.ToBase64String(BitConverter.GetBytes((long)val.Data))); - XMLwrt.WriteEndElement(); - break; - } - XMLwrt.WriteEndElement();//value - } - private static void WriteStructValue(XmlWriter XMLwrt, XmlRpcValueStruct val) - { - XMLwrt.WriteStartElement("value");//value - XMLwrt.WriteStartElement("struct");//struct - foreach (XmlRpcStructMember member in val.Members) - { - XMLwrt.WriteStartElement("member");//member - - XMLwrt.WriteStartElement("name");//name - XMLwrt.WriteString(member.Name); - XMLwrt.WriteEndElement();//name - - if (member.Data is XmlRpcValueBasic) - { - WriteBasicValue(XMLwrt, (XmlRpcValueBasic)member.Data); - } - else if (member.Data is XmlRpcValueStruct) - { - WriteStructValue(XMLwrt, (XmlRpcValueStruct)member.Data); - } - else if (member.Data is XmlRpcValueArray) - { - WriteArrayValue(XMLwrt, (XmlRpcValueArray)member.Data); - } - - XMLwrt.WriteEndElement();//member - } - XMLwrt.WriteEndElement();//struct - XMLwrt.WriteEndElement();//value - } - private static void WriteArrayValue(XmlWriter XMLwrt, XmlRpcValueArray val) - { - XMLwrt.WriteStartElement("value");//value - XMLwrt.WriteStartElement("array");//array - XMLwrt.WriteStartElement("data");//data - foreach (IXmlRpcValue o in val.Values) - { - if (o is XmlRpcValueBasic) - { - WriteBasicValue(XMLwrt, (XmlRpcValueBasic)o); - } - else if (o is XmlRpcValueStruct) - { - WriteStructValue(XMLwrt, (XmlRpcValueStruct)o); - } - else if (o is XmlRpcValueArray) - { - WriteArrayValue(XMLwrt, (XmlRpcValueArray)o); - } - } - XMLwrt.WriteEndElement();//data - XMLwrt.WriteEndElement();//array - XMLwrt.WriteEndElement();//value - } - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private static string ReadString(XmlReader reader) - { - if (reader.NodeType == XmlNodeType.Element) - { - return reader.ReadElementContentAsString(); - } - return reader.ReadContentAsString(); - } - - private static IXmlRpcValue ReadValue(XmlReader xmlReader, bool skipRead = false) - { - while (skipRead || xmlReader.Read()) - { - if (xmlReader.Name == "value" && xmlReader.IsStartElement()) - { - xmlReader.Read(); - if (xmlReader.Name == "string" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(ReadString(xmlReader), XmlRpcBasicValueType.String); - } - else if (xmlReader.Name == "int" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(int.Parse(ReadString(xmlReader), UsCulture), XmlRpcBasicValueType.Int); - } - else if (xmlReader.Name == "boolean" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(ReadString(xmlReader) == "1", XmlRpcBasicValueType.Boolean); - } - else if (xmlReader.Name == "double" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(double.Parse(ReadString(xmlReader), UsCulture), XmlRpcBasicValueType.Double); - } - else if (xmlReader.Name == "dateTime.iso8601" && xmlReader.IsStartElement()) - { - string date = ReadString(xmlReader); - int year = int.Parse(date.Substring(0, 4), UsCulture); - int month = int.Parse(date.Substring(4, 2), UsCulture); - int day = int.Parse(date.Substring(6, 2), UsCulture); - int hour = int.Parse(date.Substring(9, 2), UsCulture); - int minute = int.Parse(date.Substring(12, 2), UsCulture);//19980717T14:08:55 - int sec = int.Parse(date.Substring(15, 2), UsCulture); - var time = new DateTime(year, month, day, hour, minute, sec); - return new XmlRpcValueBasic(time, XmlRpcBasicValueType.dateTime_iso8601); - } - else if (xmlReader.Name == "base64" && xmlReader.IsStartElement()) - { - return new XmlRpcValueBasic(BitConverter.ToInt64(Convert.FromBase64String(ReadString(xmlReader)), 0) - , XmlRpcBasicValueType.Double); - } - else if (xmlReader.Name == "struct" && xmlReader.IsStartElement()) - { - var strct = new XmlRpcValueStruct(new List()); - // Read members... - while (xmlReader.Read()) - { - if (xmlReader.Name == "member" && xmlReader.IsStartElement()) - { - var member = new XmlRpcStructMember("", null); - xmlReader.Read();// read name - member.Name = ReadString(xmlReader); - - IXmlRpcValue val = ReadValue(xmlReader, true); - if (val != null) - { - member.Data = val; - strct.Members.Add(member); - } - } - else if (xmlReader.Name == "struct" && !xmlReader.IsStartElement()) - { - return strct; - } - } - return strct; - } - else if (xmlReader.Name == "array" && xmlReader.IsStartElement()) - { - var array = new XmlRpcValueArray(); - // Read members... - while (xmlReader.Read()) - { - if (xmlReader.Name == "array" && !xmlReader.IsStartElement()) - { - return array; - } - else - { - IXmlRpcValue val = ReadValue(xmlReader); - if (val != null) - array.Values.Add(val); - } - } - return array; - } - } - else break; - - if (skipRead) - { - return null; - } - } - return null; - } - } -} From 3c4043199accbfe7995dd6060c89fc837300884a Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 12 Mar 2019 09:18:45 -0400 Subject: [PATCH 150/166] Implement review feedback --- Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs | 4 ++-- Emby.Server.Implementations/ApplicationHost.cs | 2 +- Jellyfin.Server/Program.cs | 2 +- Jellyfin.Server/StartupOptions.cs | 2 +- MediaBrowser.Common/Configuration/IApplicationPaths.cs | 4 ++-- MediaBrowser.Model/System/SystemInfo.cs | 4 ++-- MediaBrowser.WebDashboard/Api/DashboardService.cs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index fd9ce3a364..00cfa0c9a9 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -36,9 +36,9 @@ namespace Emby.Server.Implementations.AppBase public string ProgramDataPath { get; private set; } /// - /// Gets the path to the web resources folder + /// Gets the path to the web UI resources folder /// - /// The web resources path. + /// The web UI resources path. public string WebPath { get; set; } /// diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index f240ef871a..14c700cc5d 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -621,7 +621,7 @@ namespace Emby.Server.Implementations string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; if (string.IsNullOrEmpty(contentRoot)) { - contentRoot = Path.Combine(ServerConfigurationManager.ApplicationPaths.WebPath, "src"); + contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } var host = new WebHostBuilder() diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 8f1498b13b..7534a6398f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -277,7 +277,7 @@ namespace Jellyfin.Server if (string.IsNullOrEmpty(webDir)) { // Use default location under ResourcesPath - webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web"); + webDir = Path.Combine(AppContext.BaseDirectory, "jellyfin-web", "src"); } } diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 2b01c7bbdf..345a8a731f 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -11,7 +11,7 @@ namespace Jellyfin.Server [Option('d', "datadir", Required = false, HelpText = "Path to use for the data folder (database files, etc.).")] public string DataDir { get; set; } - [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web resources.")] + [Option('w', "webdir", Required = false, HelpText = "Path to the Jellyfin web UI resources.")] public string WebDir { get; set; } [Option('C', "cachedir", Required = false, HelpText = "Path to use for caching.")] diff --git a/MediaBrowser.Common/Configuration/IApplicationPaths.cs b/MediaBrowser.Common/Configuration/IApplicationPaths.cs index 1f56c98fd8..fd11bf904f 100644 --- a/MediaBrowser.Common/Configuration/IApplicationPaths.cs +++ b/MediaBrowser.Common/Configuration/IApplicationPaths.cs @@ -12,9 +12,9 @@ namespace MediaBrowser.Common.Configuration string ProgramDataPath { get; } /// - /// Gets the path to the web resources folder + /// Gets the path to the web UI resources folder /// - /// The web resources path. + /// The web UI resources path. string WebPath { get; } /// diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index fa72624819..222c10798a 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -84,9 +84,9 @@ namespace MediaBrowser.Model.System public string ProgramDataPath { get; set; } /// - /// Gets or sets the web resources path. + /// Gets or sets the web UI resources path. /// - /// The web resources path. + /// The web UI resources path. public string WebPath { get; set; } /// diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 05df54b115..01cbe2c314 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.WebDashboard.Api return _serverConfigurationManager.Configuration.DashboardSourcePath; } - return Path.Combine(_serverConfigurationManager.ApplicationPaths.WebPath, "src"); + return _serverConfigurationManager.ApplicationPaths.WebPath; } } From afdef163ea53db1aa740cf4051322ff2fe190022 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Tue, 12 Mar 2019 20:49:29 +0100 Subject: [PATCH 151/166] Fix build by removing non existent namespace Looks like a wrong auto merge. (We really should fix CI) --- MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 3eb32fe16b..aee9921034 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -15,7 +15,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Xml; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; From b864e9da2a512250eff48baae28251db5a508f8f Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Tue, 12 Mar 2019 22:09:18 +0000 Subject: [PATCH 152/166] Finalise removal of --ffprobe switch Removed --ffprobe from src files and server/docker scripts. --- Dockerfile | 3 +-- Dockerfile.arm | 3 +-- Dockerfile.arm64 | 3 +-- Emby.Server.Implementations/ApplicationHost.cs | 1 - Emby.Server.Implementations/IStartupOptions.cs | 5 ----- Jellyfin.Server/StartupOptions.cs | 3 --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 9 --------- deployment/debian-package-x64/pkg-src/conf/jellyfin | 3 +-- deployment/debian-package-x64/pkg-src/jellyfin.service | 2 +- deployment/fedora-package-x64/pkg-src/jellyfin.env | 1 - deployment/fedora-package-x64/pkg-src/jellyfin.service | 2 +- 11 files changed, 6 insertions(+), 29 deletions(-) diff --git a/Dockerfile b/Dockerfile index 91a4f5a2d2..72c0cdbf3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,5 +31,4 @@ VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ --datadir /config \ --cachedir /cache \ - --ffmpeg /usr/local/bin/ffmpeg \ - --ffprobe /usr/local/bin/ffprobe + --ffmpeg /usr/local/bin/ffmpeg diff --git a/Dockerfile.arm b/Dockerfile.arm index 42f0354a32..238e90ccd8 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -39,5 +39,4 @@ VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ --datadir /config \ --cachedir /cache \ - --ffmpeg /usr/bin/ffmpeg \ - --ffprobe /usr/bin/ffprobe + --ffmpeg /usr/bin/ffmpeg diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index d3103d3893..2927d4e658 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -40,5 +40,4 @@ VOLUME /cache /config /media ENTRYPOINT dotnet /jellyfin/jellyfin.dll \ --datadir /config \ --cachedir /cache \ - --ffmpeg /usr/bin/ffmpeg \ - --ffprobe /usr/bin/ffprobe + --ffmpeg /usr/bin/ffmpeg diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 98f6ab6bc7..1810064e69 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -837,7 +837,6 @@ namespace Emby.Server.Implementations LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, - StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, diff --git a/Emby.Server.Implementations/IStartupOptions.cs b/Emby.Server.Implementations/IStartupOptions.cs index 24aaa76c02..6e915de3d1 100644 --- a/Emby.Server.Implementations/IStartupOptions.cs +++ b/Emby.Server.Implementations/IStartupOptions.cs @@ -7,11 +7,6 @@ namespace Emby.Server.Implementations /// string FFmpegPath { get; } - /// - /// --ffprobe - /// - string FFprobePath { get; } - /// /// --service /// diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs index 345a8a731f..8296d414ef 100644 --- a/Jellyfin.Server/StartupOptions.cs +++ b/Jellyfin.Server/StartupOptions.cs @@ -26,9 +26,6 @@ namespace Jellyfin.Server [Option("ffmpeg", Required = false, HelpText = "Path to external FFmpeg executable to use in place of default found in PATH.")] public string FFmpegPath { get; set; } - [Option("ffprobe", Required = false, HelpText = "(deprecated) Option has no effect and shall be removed in next release.")] - public string FFprobePath { get; set; } - [Option("service", Required = false, HelpText = "Run as headless service.")] public bool IsService { get; set; } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 94beda3dbb..f454c27235 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -51,7 +51,6 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly IProcessFactory _processFactory; private readonly int DefaultImageExtractionTimeoutMs; private readonly string StartupOptionFFmpegPath; - private readonly string StartupOptionFFprobePath; private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1); private readonly List _runningProcesses = new List(); @@ -60,7 +59,6 @@ namespace MediaBrowser.MediaEncoding.Encoder ILoggerFactory loggerFactory, IJsonSerializer jsonSerializer, string startupOptionsFFmpegPath, - string startupOptionsFFprobePath, IServerConfigurationManager configurationManager, IFileSystem fileSystem, Func subtitleEncoder, @@ -71,7 +69,6 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = loggerFactory.CreateLogger(nameof(MediaEncoder)); _jsonSerializer = jsonSerializer; StartupOptionFFmpegPath = startupOptionsFFmpegPath; - StartupOptionFFprobePath = startupOptionsFFprobePath; ConfigurationManager = configurationManager; FileSystem = fileSystem; SubtitleEncoder = subtitleEncoder; @@ -86,12 +83,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public void SetFFmpegPath() { - // ToDo - Finalise removal of the --ffprobe switch - if (!string.IsNullOrEmpty(StartupOptionFFprobePath)) - { - _logger.LogWarning("--ffprobe switch is deprecated and shall be removed in the next release"); - } - // 1) Custom path stored in config/encoding xml file under tag takes precedence if (!ValidatePath(ConfigurationManager.GetConfiguration("encoding").EncoderAppPath, FFmpegLocation.Custom)) { diff --git a/deployment/debian-package-x64/pkg-src/conf/jellyfin b/deployment/debian-package-x64/pkg-src/conf/jellyfin index 7db482d428..bc00c37e20 100644 --- a/deployment/debian-package-x64/pkg-src/conf/jellyfin +++ b/deployment/debian-package-x64/pkg-src/conf/jellyfin @@ -23,7 +23,6 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/lib/jellyfin/restart.sh" # ffmpeg binary paths, overriding the system values JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/share/jellyfin-ffmpeg/ffmpeg" -JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/share/jellyfin-ffmpeg/ffprobe" # [OPTIONAL] run Jellyfin as a headless service #JELLYFIN_SERVICE_OPT="--service" @@ -38,4 +37,4 @@ JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/share/jellyfin-ffmpeg/ffprobe" # Application username JELLYFIN_USER="jellyfin" # Full application command -JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_FFPROBE_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" +JELLYFIN_ARGS="$JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT" diff --git a/deployment/debian-package-x64/pkg-src/jellyfin.service b/deployment/debian-package-x64/pkg-src/jellyfin.service index b4da3a945b..1305e238b0 100644 --- a/deployment/debian-package-x64/pkg-src/jellyfin.service +++ b/deployment/debian-package-x64/pkg-src/jellyfin.service @@ -6,7 +6,7 @@ After = network.target Type = simple EnvironmentFile = /etc/default/jellyfin User = jellyfin -ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart = /usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} Restart = on-failure TimeoutSec = 15 diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.env b/deployment/fedora-package-x64/pkg-src/jellyfin.env index 143a317c43..de48f13af5 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.env +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.env @@ -25,7 +25,6 @@ JELLYFIN_RESTART_OPT="--restartpath=/usr/libexec/jellyfin/restart.sh" # [OPTIONAL] ffmpeg binary paths, overriding the UI-configured values #JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/bin/ffmpeg" -#JELLYFIN_FFPROBE_OPT="--ffprobe=/usr/bin/ffprobe" # [OPTIONAL] run Jellyfin as a headless service #JELLYFIN_SERVICE_OPT="--service" diff --git a/deployment/fedora-package-x64/pkg-src/jellyfin.service b/deployment/fedora-package-x64/pkg-src/jellyfin.service index 1f83d3d386..f3dc594b1c 100644 --- a/deployment/fedora-package-x64/pkg-src/jellyfin.service +++ b/deployment/fedora-package-x64/pkg-src/jellyfin.service @@ -5,7 +5,7 @@ Description=Jellyfin is a free software media system that puts you in control of [Service] EnvironmentFile=/etc/sysconfig/jellyfin WorkingDirectory=/var/lib/jellyfin -ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_FFPROBE_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} +ExecStart=/usr/bin/jellyfin ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} TimeoutSec=15 Restart=on-failure User=jellyfin From 4cc4d57a782441f02f8901af29634ffbfd4a2fc2 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Wed, 13 Mar 2019 20:37:08 +0100 Subject: [PATCH 153/166] Disable the Drone CI ABI check --- .drone.yml | 81 ------------------------------------------------------ 1 file changed, 81 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7705f4f936..87c8e414e9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -28,84 +28,3 @@ steps: commands: - dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release" ---- - -kind: pipeline -name: check-abi - -steps: -- name: submodules - image: docker:git - commands: - - git submodule update --init --recursive - -- name: build - image: microsoft/dotnet:2-sdk - commands: - - dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release" - -- name: clone-dotnet-compat - image: docker:git - commands: - - git clone --depth 1 https://github.com/EraYaN/dotnet-compatibility ci/dotnet-compatibility - -- name: build-dotnet-compat - image: microsoft/dotnet:2-sdk - commands: - - dotnet publish "ci/dotnet-compatibility/CompatibilityCheckerCoreCLI" --configuration Release --output "../../ci-tools" - -- name: download-last-nuget-release-common - image: plugins/download - settings: - source: https://www.nuget.org/api/v2/package/Jellyfin.Common - destination: ci/Jellyfin.Common.nupkg - -- name: download-last-nuget-release-model - image: plugins/download - settings: - source: https://www.nuget.org/api/v2/package/Jellyfin.Model - destination: ci/Jellyfin.Model.nupkg - -- name: download-last-nuget-release-controller - image: plugins/download - settings: - source: https://www.nuget.org/api/v2/package/Jellyfin.Controller - destination: ci/Jellyfin.Controller.nupkg - -- name: download-last-nuget-release-naming - image: plugins/download - settings: - source: https://www.nuget.org/api/v2/package/Jellyfin.Naming - destination: ci/Jellyfin.Naming.nupkg - -- name: extract-downloaded-nuget-packages - image: garthk/unzip - commands: - - unzip -j ci/Jellyfin.Common.nupkg "*.dll" -d ci/nuget-packages - - unzip -j ci/Jellyfin.Model.nupkg "*.dll" -d ci/nuget-packages - - unzip -j ci/Jellyfin.Controller.nupkg "*.dll" -d ci/nuget-packages - - unzip -j ci/Jellyfin.Naming.nupkg "*.dll" -d ci/nuget-packages - -- name: run-dotnet-compat-common - image: microsoft/dotnet:2-runtime - err_ignore: true - commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Common.dll ci/ci-release/MediaBrowser.Common.dll - -- name: run-dotnet-compat-model - image: microsoft/dotnet:2-runtime - err_ignore: true - commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Model.dll ci/ci-release/MediaBrowser.Model.dll - -- name: run-dotnet-compat-controller - image: microsoft/dotnet:2-runtime - err_ignore: true - commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Controller.dll ci/ci-release/MediaBrowser.Controller.dll - -- name: run-dotnet-compat-naming - image: microsoft/dotnet:2-runtime - err_ignore: true - commands: - - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Emby.Naming.dll ci/ci-release/Emby.Naming.dll From 1d443d2ff5ef9edaf7040633ec737d043afeafa6 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Wed, 13 Mar 2019 21:09:08 +0100 Subject: [PATCH 154/166] Do not use the nuget packages for comparison, but the last master build (#1091) * Do not use the nuget packages for comparison, but the last master build. * Only allow passing builds. --- .ci/azure-pipelines.yml | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index 4cd19dc838..7a92d40889 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -11,9 +11,6 @@ pr: trigger: batch: true - branches: - include: - - master jobs: - job: main_build @@ -71,28 +68,28 @@ jobs: - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Naming' - condition: eq(variables['BuildConfiguration'], 'Release') + condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded()) inputs: PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll' artifactName: 'Jellyfin.Naming' - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Controller' - condition: eq(variables['BuildConfiguration'], 'Release') + condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded()) inputs: PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll' artifactName: 'Jellyfin.Controller' - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Model' - condition: eq(variables['BuildConfiguration'], 'Release') + condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded()) inputs: PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll' artifactName: 'Jellyfin.Model' - task: PublishBuildArtifacts@1 displayName: 'Publish Artifact Common' - condition: eq(variables['BuildConfiguration'], 'Release') + condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded()) inputs: PathtoPublish: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll' artifactName: 'Jellyfin.Common' @@ -102,7 +99,7 @@ jobs: pool: vmImage: ubuntu-16.04 dependsOn: main_build - condition: succeeded() + condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds) strategy: matrix: Naming: @@ -121,16 +118,28 @@ jobs: steps: - checkout: none - - task: NuGetCommand@2 - displayName: 'Download $(NugetPackageName)' + - task: DownloadBuildArtifacts@0 + displayName: Download the Reference Assembly Build Artifact inputs: - command: custom - arguments: 'install $(NugetPackageName) -OutputDirectory $(System.ArtifactsDirectory)/packages -ExcludeVersion -DirectDownload' + buildType: 'specific' # Options: current, specific + project: $(System.TeamProjectId) # Required when buildType == Specific + pipeline: $(System.DefinitionId) # Required when buildType == Specific, not sure if this will take a name too + #specificBuildWithTriggering: false # Optional + buildVersionToDownload: 'latestFromBranch' # Required when buildType == Specific# Options: latest, latestFromBranch, specific + allowPartiallySucceededBuilds: false # Optional + branchName: '$(System.PullRequest.TargetBranch)' # Required when buildType == Specific && BuildVersionToDownload == LatestFromBranch + #buildId: # Required when buildType == Specific && BuildVersionToDownload == Specific + #tags: # Optional + downloadType: 'single' # Options: single, specific + artifactName: '$(NugetPackageName)'# Required when downloadType == Single + #itemPattern: '**' # Optional + downloadPath: '$(System.ArtifactsDirectory)/current-artifacts' + #parallelizationLimit: '8' # Optional - task: CopyFiles@2 displayName: Copy Nuget Assembly to current-release folder inputs: - sourceFolder: $(System.ArtifactsDirectory)/packages/$(NugetPackageName) # Optional + sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional contents: '**/*.dll' targetFolder: $(System.ArtifactsDirectory)/current-release cleanTargetFolder: true # Optional @@ -138,7 +147,7 @@ jobs: flattenFolders: true # Optional - task: DownloadBuildArtifacts@0 - displayName: Download the Assembly Build Artifact + displayName: Download the New Assembly Build Artifact inputs: buildType: 'current' # Options: current, specific allowPartiallySucceededBuilds: false # Optional From e64aaebbacfa7a720c99ca2ab1aa11f7fcd63868 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Mar 2019 17:51:33 +0100 Subject: [PATCH 155/166] Improvements around streams * Use ArrayPool instead of allocating new buffers each time * Remove NetworkStream copy * Remove some dead code --- .../HttpServer/StreamWriter.cs | 1 - .../IO/StreamHelper.cs | 234 +++++++++++------- .../HdHomerun/HdHomerunUdpStream.cs | 3 +- .../LiveTv/TunerHosts/LiveStream.cs | 58 ++--- .../Net/DisposableManagedObjectBase.cs | 66 ----- .../Net/SocketFactory.cs | 134 +++------- Emby.Server.Implementations/Net/UdpSocket.cs | 74 +++--- MediaBrowser.Api/LiveTv/LiveTvService.cs | 32 ++- .../LiveTv/ProgressiveFileCopier.cs | 39 +-- MediaBrowser.Model/Net/HttpResponse.cs | 64 ----- MediaBrowser.Model/Net/IAcceptSocket.cs | 15 -- 11 files changed, 269 insertions(+), 451 deletions(-) delete mode 100644 Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs delete mode 100644 MediaBrowser.Model/Net/HttpResponse.cs delete mode 100644 MediaBrowser.Model/Net/IAcceptSocket.cs diff --git a/Emby.Server.Implementations/HttpServer/StreamWriter.cs b/Emby.Server.Implementations/HttpServer/StreamWriter.cs index 66a13e20df..cf30bbc326 100644 --- a/Emby.Server.Implementations/HttpServer/StreamWriter.cs +++ b/Emby.Server.Implementations/HttpServer/StreamWriter.cs @@ -5,7 +5,6 @@ using System.IO; 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 diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs index 09cf4d4a3e..d02cd84a03 100644 --- a/Emby.Server.Implementations/IO/StreamHelper.cs +++ b/Emby.Server.Implementations/IO/StreamHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -8,168 +9,213 @@ namespace Emby.Server.Implementations.IO { public class StreamHelper : IStreamHelper { + private const int StreamCopyToBufferSize = 81920; + public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, Action onStarted, CancellationToken cancellationToken) { - byte[] buffer = new byte[bufferSize]; - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try { - cancellationToken.ThrowIfCancellationRequested(); - - await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); - - if (onStarted != null) + int read; + while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) { - onStarted(); - onStarted = null; + cancellationToken.ThrowIfCancellationRequested(); + + await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + + if (onStarted != null) + { + onStarted(); + onStarted = null; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken) { - byte[] buffer = new byte[bufferSize]; - - if (emptyReadLimit <= 0) + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try { - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) != 0) + if (emptyReadLimit <= 0) + { + int read; + while ((read = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) != 0) + { + cancellationToken.ThrowIfCancellationRequested(); + + await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); + } + + return; + } + + var eofCount = 0; + + while (eofCount < emptyReadLimit) { cancellationToken.ThrowIfCancellationRequested(); - await destination.WriteAsync(buffer, 0, read).ConfigureAwait(false); - } + var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); - return; + if (bytesRead == 0) + { + eofCount++; + await Task.Delay(50, cancellationToken).ConfigureAwait(false); + } + else + { + eofCount = 0; + + await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); + } + } } - - var eofCount = 0; - - while (eofCount < emptyReadLimit) + finally { - cancellationToken.ThrowIfCancellationRequested(); - - var bytesRead = source.Read(buffer, 0, buffer.Length); - - if (bytesRead == 0) - { - eofCount++; - await Task.Delay(50, cancellationToken).ConfigureAwait(false); - } - else - { - eofCount = 0; - - await destination.WriteAsync(buffer, 0, bytesRead).ConfigureAwait(false); - } + ArrayPool.Shared.Return(buffer); } } - const int StreamCopyToBufferSize = 81920; public async Task CopyToAsync(Stream source, Stream destination, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = bytesRead; + int totalBytesRead = 0; - if (bytesToWrite > 0) + int bytesRead; + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + var bytesToWrite = bytesRead; - totalBytesRead += bytesRead; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } } - } - return totalBytesRead; + return totalBytesRead; + } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - int totalBytesRead = 0; - - while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = bytesRead; + int bytesRead; + int totalBytesRead = 0; - if (bytesToWrite > 0) + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + var bytesToWrite = bytesRead; - totalBytesRead += bytesRead; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + + totalBytesRead += bytesRead; + } } - } - return totalBytesRead; + return totalBytesRead; + } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsyncWithSyncRead(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - - while ((bytesRead = source.Read(array, 0, array.Length)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = Math.Min(bytesRead, copyLength); + int bytesRead; - if (bytesToWrite > 0) + while ((bytesRead = source.Read(buffer, 0, buffer.Length)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - } + var bytesToWrite = Math.Min(bytesRead, copyLength); - copyLength -= bytesToWrite; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } - if (copyLength <= 0) - { - break; + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken) { - var array = new byte[StreamCopyToBufferSize]; - int bytesRead; - - while ((bytesRead = await source.ReadAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(false)) != 0) + byte[] buffer = ArrayPool.Shared.Rent(StreamCopyToBufferSize); + try { - var bytesToWrite = Math.Min(bytesRead, copyLength); + int bytesRead; - if (bytesToWrite > 0) + while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) { - await destination.WriteAsync(array, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); - } + var bytesToWrite = Math.Min(bytesRead, copyLength); - copyLength -= bytesToWrite; + if (bytesToWrite > 0) + { + await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false); + } - if (copyLength <= 0) - { - break; + copyLength -= bytesToWrite; + + if (copyLength <= 0) + { + break; + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } public async Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken) { - byte[] buffer = new byte[bufferSize]; - - while (!cancellationToken.IsCancellationRequested) + byte[] buffer = ArrayPool.Shared.Rent(bufferSize); + try { - var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false); - - //var position = fs.Position; - //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - - if (bytesRead == 0) + while (!cancellationToken.IsCancellationRequested) { - await Task.Delay(100).ConfigureAwait(false); + var bytesRead = await CopyToAsyncInternal(source, target, buffer, cancellationToken).ConfigureAwait(false); + + if (bytesRead == 0) + { + await Task.Delay(100).ConfigureAwait(false); + } } } + finally + { + ArrayPool.Shared.Return(buffer); + } } private static async Task CopyToAsyncInternal(Stream source, Stream destination, byte[] buffer, CancellationToken cancellationToken) diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs index 8774371d52..7f426ea31f 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs @@ -151,7 +151,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun }); } - private static int RtpHeaderBytes = 12; + private const int RtpHeaderBytes = 12; + private async Task CopyTo(MediaBrowser.Model.Net.ISocket udpClient, string file, TaskCompletionSource openTaskCompletionSource, CancellationToken cancellationToken) { var bufferSize = 81920; diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs index 1f8ca276e7..ece2cbd547 100644 --- a/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs +++ b/Emby.Server.Implementations/LiveTv/TunerHosts/LiveStream.cs @@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts public string OriginalStreamId { get; set; } public bool EnableStreamSharing { get; set; } - public string UniqueId { get; private set; } + public string UniqueId { get; } protected readonly IFileSystem FileSystem; protected readonly IServerApplicationPaths AppPaths; @@ -31,12 +31,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts protected readonly ILogger Logger; protected readonly CancellationTokenSource LiveStreamCancellationTokenSource = new CancellationTokenSource(); - public string TunerHostId { get; private set; } + public string TunerHostId { get; } public DateTime DateOpened { get; protected set; } - public Func OnClose { get; set; } - public LiveStream(MediaSourceInfo mediaSource, TunerHostInfo tuner, IFileSystem fileSystem, ILogger logger, IServerApplicationPaths appPaths) { OriginalMediaSource = mediaSource; @@ -76,26 +74,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts LiveStreamCancellationTokenSource.Cancel(); - if (OnClose != null) - { - return CloseWithExternalFn(); - } - return Task.CompletedTask; } - private async Task CloseWithExternalFn() - { - try - { - await OnClose().ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error closing live stream"); - } - } - protected Stream GetInputStream(string path, bool allowAsyncFileRead) { var fileOpenOptions = FileOpenOptions.SequentialScan; @@ -113,27 +94,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts return DeleteTempFiles(GetStreamFilePaths()); } - protected async Task DeleteTempFiles(List paths, int retryCount = 0) + protected async Task DeleteTempFiles(IEnumerable paths, int retryCount = 0) { if (retryCount == 0) { - Logger.LogInformation("Deleting temp files {0}", string.Join(", ", paths.ToArray())); + Logger.LogInformation("Deleting temp files {0}", paths); } var failedFiles = new List(); foreach (var path in paths) { + if (!File.Exists(path)) + { + continue; + } + try { FileSystem.DeleteFile(path); } - catch (DirectoryNotFoundException) - { - } - catch (FileNotFoundException) - { - } catch (Exception ex) { Logger.LogError(ex, "Error deleting file {path}", path); @@ -157,8 +137,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts { cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, LiveStreamCancellationTokenSource.Token).Token; - var allowAsync = false; - // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 + // use non-async filestream on windows along with read due to https://github.com/dotnet/corefx/issues/6039 + var allowAsync = Environment.OSVersion.Platform != PlatformID.Win32NT; bool seekFile = (DateTime.UtcNow - DateOpened).TotalSeconds > 10; @@ -181,28 +161,24 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts Logger.LogInformation("Live Stream ended."); } - private Tuple GetNextFile(string currentFile) + private (string file, bool isLastFile) GetNextFile(string currentFile) { var files = GetStreamFilePaths(); - //logger.LogInformation("Live stream files: {0}", string.Join(", ", files.ToArray())); - if (string.IsNullOrEmpty(currentFile)) { - return new Tuple(files.Last(), true); + return (files.Last(), true); } var nextIndex = files.FindIndex(i => string.Equals(i, currentFile, StringComparison.OrdinalIgnoreCase)) + 1; var isLastFile = nextIndex == files.Count - 1; - return new Tuple(files.ElementAtOrDefault(nextIndex), isLastFile); + return (files.ElementAtOrDefault(nextIndex), isLastFile); } private async Task CopyFile(string path, bool seekFile, int emptyReadLimit, bool allowAsync, Stream stream, CancellationToken cancellationToken) { - //logger.LogInformation("Opening live stream file {0}. Empty read limit: {1}", path, emptyReadLimit); - using (var inputStream = (FileStream)GetInputStream(path, allowAsync)) { if (seekFile) @@ -218,7 +194,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts private void TrySeek(FileStream stream, long offset) { - //logger.LogInformation("TrySeek live stream"); + if (!stream.CanSeek) + { + return; + } + try { stream.Seek(offset, SeekOrigin.End); diff --git a/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs b/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs deleted file mode 100644 index 304b445651..0000000000 --- a/Emby.Server.Implementations/Net/DisposableManagedObjectBase.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; - -namespace Emby.Server.Implementations.Net -{ - /// - /// Correclty implements the interface and pattern for an object containing only managed resources, and adds a few common niceities not on the interface such as an property. - /// - public abstract class DisposableManagedObjectBase : IDisposable - { - - #region Public Methods - - /// - /// Override this method and dispose any objects you own the lifetime of if disposing is true; - /// - /// True if managed objects should be disposed, if false, only unmanaged resources should be released. - protected abstract void Dispose(bool disposing); - - - //TODO Remove and reimplement using the IsDisposed property directly. - /// - /// Throws an if the property is true. - /// - /// - /// Thrown if the property is true. - /// - protected virtual void ThrowIfDisposed() - { - if (IsDisposed) throw new ObjectDisposedException(GetType().Name); - } - - #endregion - - #region Public Properties - - /// - /// Sets or returns a boolean indicating whether or not this instance has been disposed. - /// - /// - public bool IsDisposed - { - get; - private set; - } - - #endregion - - #region IDisposable Members - - /// - /// Disposes this object instance and all internally managed resources. - /// - /// - /// Sets the property to true. Does not explicitly throw an exception if called multiple times, but makes no promises about behaviour of derived classes. - /// - /// - public void Dispose() - { - IsDisposed = true; - - Dispose(true); - } - - #endregion - } -} diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs index 6beb14f558..492f48abe8 100644 --- a/Emby.Server.Implementations/Net/SocketFactory.cs +++ b/Emby.Server.Implementations/Net/SocketFactory.cs @@ -4,7 +4,6 @@ using System.Net; using System.Net.Sockets; using Emby.Server.Implementations.Networking; using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.Net { @@ -19,7 +18,10 @@ namespace Emby.Server.Implementations.Net public ISocket CreateTcpSocket(IpAddressInfo remoteAddress, int remotePort) { - if (remotePort < 0) throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort)); + if (remotePort < 0) + { + throw new ArgumentException("remotePort cannot be less than zero.", nameof(remotePort)); + } var addressFamily = remoteAddress.AddressFamily == IpAddressFamily.InterNetwork ? AddressFamily.InterNetwork @@ -42,8 +44,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -55,7 +56,10 @@ namespace Emby.Server.Implementations.Net /// An integer specifying the local port to bind the acceptSocket to. public ISocket CreateUdpSocket(int localPort) { - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try @@ -65,8 +69,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -74,7 +77,10 @@ namespace Emby.Server.Implementations.Net public ISocket CreateUdpBroadcastSocket(int localPort) { - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try @@ -86,8 +92,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -99,7 +104,10 @@ namespace Emby.Server.Implementations.Net /// An implementation of the interface used by RSSDP components to perform acceptSocket operations. public ISocket CreateSsdpUdpSocket(IpAddressInfo localIpAddress, int localPort) { - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); try @@ -114,8 +122,7 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } @@ -130,10 +137,25 @@ namespace Emby.Server.Implementations.Net /// public ISocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) { - if (ipAddress == null) throw new ArgumentNullException(nameof(ipAddress)); - if (ipAddress.Length == 0) throw new ArgumentException("ipAddress cannot be an empty string.", nameof(ipAddress)); - if (multicastTimeToLive <= 0) throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive)); - if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + if (ipAddress == null) + { + throw new ArgumentNullException(nameof(ipAddress)); + } + + if (ipAddress.Length == 0) + { + throw new ArgumentException("ipAddress cannot be an empty string.", nameof(ipAddress)); + } + + if (multicastTimeToLive <= 0) + { + throw new ArgumentException("multicastTimeToLive cannot be zero or less.", nameof(multicastTimeToLive)); + } + + if (localPort < 0) + { + throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort)); + } var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); @@ -172,87 +194,13 @@ namespace Emby.Server.Implementations.Net } catch { - if (retVal != null) - retVal.Dispose(); + retVal?.Dispose(); throw; } } public Stream CreateNetworkStream(ISocket socket, bool ownsSocket) - { - var netSocket = (UdpSocket)socket; - - return new SocketStream(netSocket.Socket, ownsSocket); - } + => new NetworkStream(((UdpSocket)socket).Socket, ownsSocket); } - - public class SocketStream : Stream - { - private readonly Socket _socket; - - public SocketStream(Socket socket, bool ownsSocket) - { - _socket = socket; - } - - public override void Flush() - { - } - - public override bool CanRead => true; - - public override bool CanSeek => false; - - public override bool CanWrite => true; - - public override long Length => throw new NotImplementedException(); - - public override long Position - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - _socket.Send(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - _socket.EndSend(asyncResult); - } - - public override void SetLength(long value) - { - throw new NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _socket.Receive(buffer, offset, count, SocketFlags.None); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - return _socket.EndReceive(asyncResult); - } - } - } diff --git a/Emby.Server.Implementations/Net/UdpSocket.cs b/Emby.Server.Implementations/Net/UdpSocket.cs index d488554860..6c55085c83 100644 --- a/Emby.Server.Implementations/Net/UdpSocket.cs +++ b/Emby.Server.Implementations/Net/UdpSocket.cs @@ -11,12 +11,15 @@ namespace Emby.Server.Implementations.Net // THIS IS A LINKED FILE - SHARED AMONGST MULTIPLE PLATFORMS // Be careful to check any changes compile and work for all platform projects it is shared in. - public sealed class UdpSocket : DisposableManagedObjectBase, ISocket + public sealed class UdpSocket : ISocket, IDisposable { - private Socket _Socket; - private int _LocalPort; + private Socket _socket; + private int _localPort; + private bool _disposed = false; - public Socket Socket => _Socket; + public Socket Socket => _socket; + + public IpAddressInfo LocalIPAddress { get; } private readonly SocketAsyncEventArgs _receiveSocketAsyncEventArgs = new SocketAsyncEventArgs() { @@ -35,11 +38,11 @@ namespace Emby.Server.Implementations.Net { if (socket == null) throw new ArgumentNullException(nameof(socket)); - _Socket = socket; - _LocalPort = localPort; + _socket = socket; + _localPort = localPort; LocalIPAddress = NetworkManager.ToIpAddressInfo(ip); - _Socket.Bind(new IPEndPoint(ip, _LocalPort)); + _socket.Bind(new IPEndPoint(ip, _localPort)); InitReceiveSocketAsyncEventArgs(); } @@ -101,32 +104,26 @@ namespace Emby.Server.Implementations.Net { if (socket == null) throw new ArgumentNullException(nameof(socket)); - _Socket = socket; - _Socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); + _socket = socket; + _socket.Connect(NetworkManager.ToIPEndPoint(endPoint)); InitReceiveSocketAsyncEventArgs(); } - public IpAddressInfo LocalIPAddress - { - get; - private set; - } - public IAsyncResult BeginReceive(byte[] buffer, int offset, int count, AsyncCallback callback) { ThrowIfDisposed(); EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); - return _Socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer); + return _socket.BeginReceiveFrom(buffer, offset, count, SocketFlags.None, ref receivedFromEndPoint, callback, buffer); } public int Receive(byte[] buffer, int offset, int count) { ThrowIfDisposed(); - return _Socket.Receive(buffer, 0, buffer.Length, SocketFlags.None); + return _socket.Receive(buffer, 0, buffer.Length, SocketFlags.None); } public SocketReceiveResult EndReceive(IAsyncResult result) @@ -136,7 +133,7 @@ namespace Emby.Server.Implementations.Net var sender = new IPEndPoint(IPAddress.Any, 0); var remoteEndPoint = (EndPoint)sender; - var receivedBytes = _Socket.EndReceiveFrom(result, ref remoteEndPoint); + var receivedBytes = _socket.EndReceiveFrom(result, ref remoteEndPoint); var buffer = (byte[])result.AsyncState; @@ -236,37 +233,42 @@ namespace Emby.Server.Implementations.Net var ipEndPoint = NetworkManager.ToIPEndPoint(endPoint); - return _Socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state); + return _socket.BeginSendTo(buffer, offset, size, SocketFlags.None, ipEndPoint, callback, state); } public int EndSendTo(IAsyncResult result) { ThrowIfDisposed(); - return _Socket.EndSendTo(result); + return _socket.EndSendTo(result); } - protected override void Dispose(bool disposing) + private void ThrowIfDisposed() { - if (disposing) + if (_disposed) { - var socket = _Socket; - if (socket != null) - socket.Dispose(); - - var tcs = _currentReceiveTaskCompletionSource; - if (tcs != null) - { - tcs.TrySetCanceled(); - } - var sendTcs = _currentSendTaskCompletionSource; - if (sendTcs != null) - { - sendTcs.TrySetCanceled(); - } + throw new ObjectDisposedException(nameof(UdpSocket)); } } + public void Dispose() + { + if (_disposed) + { + return; + } + + _socket?.Dispose(); + _currentReceiveTaskCompletionSource?.TrySetCanceled(); + _currentSendTaskCompletionSource?.TrySetCanceled(); + + _socket = null; + _currentReceiveTaskCompletionSource = null; + _currentSendTaskCompletionSource = null; + + _disposed = true; + } + private static IpEndPointInfo ToIpEndPointInfo(IPEndPoint endpoint) { if (endpoint == null) diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index 486d5e8a71..e41ad540ad 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -23,7 +23,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Services; -using MediaBrowser.Model.System; using Microsoft.Net.Http.Headers; namespace MediaBrowser.Api.LiveTv @@ -695,27 +694,36 @@ namespace MediaBrowser.Api.LiveTv private readonly IHttpClient _httpClient; private readonly ILibraryManager _libraryManager; private readonly IDtoService _dtoService; - private readonly IFileSystem _fileSystem; private readonly IAuthorizationContext _authContext; private readonly ISessionContext _sessionContext; - private ICryptoProvider _cryptographyProvider; - private IStreamHelper _streamHelper; - private IMediaSourceManager _mediaSourceManager; + private readonly ICryptoProvider _cryptographyProvider; + private readonly IStreamHelper _streamHelper; + private readonly IMediaSourceManager _mediaSourceManager; - public LiveTvService(ICryptoProvider crypto, IMediaSourceManager mediaSourceManager, IStreamHelper streamHelper, ILiveTvManager liveTvManager, IUserManager userManager, IServerConfigurationManager config, IHttpClient httpClient, ILibraryManager libraryManager, IDtoService dtoService, IFileSystem fileSystem, IAuthorizationContext authContext, ISessionContext sessionContext) + public LiveTvService( + ICryptoProvider crypto, + IMediaSourceManager mediaSourceManager, + IStreamHelper streamHelper, + ILiveTvManager liveTvManager, + IUserManager userManager, + IServerConfigurationManager config, + IHttpClient httpClient, + ILibraryManager libraryManager, + IDtoService dtoService, + IAuthorizationContext authContext, + ISessionContext sessionContext) { + _cryptographyProvider = crypto; + _mediaSourceManager = mediaSourceManager; + _streamHelper = streamHelper; _liveTvManager = liveTvManager; _userManager = userManager; _config = config; _httpClient = httpClient; _libraryManager = libraryManager; _dtoService = dtoService; - _fileSystem = fileSystem; _authContext = authContext; _sessionContext = sessionContext; - _cryptographyProvider = crypto; - _streamHelper = streamHelper; - _mediaSourceManager = mediaSourceManager; } public object Get(GetTunerHostTypes request) @@ -729,7 +737,7 @@ namespace MediaBrowser.Api.LiveTv var user = request.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(request.UserId); var folders = _liveTvManager.GetRecordingFolders(user); - var returnArray = _dtoService.GetBaseItemDtos(folders.ToArray(), new DtoOptions(), user); + var returnArray = _dtoService.GetBaseItemDtos(folders, new DtoOptions(), user); var result = new QueryResult { @@ -754,7 +762,7 @@ namespace MediaBrowser.Api.LiveTv [HeaderNames.ContentType] = Model.Net.MimeTypes.GetMimeType(path) }; - return new ProgressiveFileCopier(_fileSystem, _streamHelper, path, outputHeaders, Logger) + return new ProgressiveFileCopier(_streamHelper, path, outputHeaders, Logger) { AllowEndOfFile = false }; diff --git a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs index 51552d928b..4c608d9a33 100644 --- a/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs +++ b/MediaBrowser.Api/LiveTv/ProgressiveFileCopier.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Threading; @@ -11,22 +12,17 @@ namespace MediaBrowser.Api.LiveTv { public class ProgressiveFileCopier : IAsyncStreamWriter, IHasHeaders { - private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly string _path; private readonly Dictionary _outputHeaders; - const int StreamCopyToBufferSize = 81920; - - public long StartPosition { get; set; } public bool AllowEndOfFile = true; private readonly IDirectStreamProvider _directStreamProvider; private IStreamHelper _streamHelper; - public ProgressiveFileCopier(IFileSystem fileSystem, IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger) + public ProgressiveFileCopier(IStreamHelper streamHelper, string path, Dictionary outputHeaders, ILogger logger) { - _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _logger = logger; @@ -43,18 +39,6 @@ namespace MediaBrowser.Api.LiveTv public IDictionary Headers => _outputHeaders; - private Stream GetInputStream(bool allowAsyncFileRead) - { - var fileOpenOptions = FileOpenOptions.SequentialScan; - - if (allowAsyncFileRead) - { - fileOpenOptions |= FileOpenOptions.Asynchronous; - } - - return _fileSystem.GetFileStream(_path, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.ReadWrite, fileOpenOptions); - } - public async Task WriteToAsync(Stream outputStream, CancellationToken cancellationToken) { if (_directStreamProvider != null) @@ -63,28 +47,23 @@ namespace MediaBrowser.Api.LiveTv return; } - var eofCount = 0; + var fileOptions = FileOptions.SequentialScan; // use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039 - var allowAsyncFileRead = true; - - using (var inputStream = GetInputStream(allowAsyncFileRead)) + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { - if (StartPosition > 0) - { - inputStream.Position = StartPosition; - } + fileOptions |= FileOptions.Asynchronous; + } + using (var inputStream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, fileOptions)) + { var emptyReadLimit = AllowEndOfFile ? 20 : 100; - + var eofCount = 0; while (eofCount < emptyReadLimit) { int bytesRead; bytesRead = await _streamHelper.CopyToAsync(inputStream, outputStream, cancellationToken).ConfigureAwait(false); - //var position = fs.Position; - //_logger.LogDebug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path); - if (bytesRead == 0) { eofCount++; diff --git a/MediaBrowser.Model/Net/HttpResponse.cs b/MediaBrowser.Model/Net/HttpResponse.cs deleted file mode 100644 index 286b1c0afd..0000000000 --- a/MediaBrowser.Model/Net/HttpResponse.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; - -namespace MediaBrowser.Model.Net -{ - public class HttpResponse : IDisposable - { - /// - /// Gets or sets the type of the content. - /// - /// The type of the content. - public string ContentType { get; set; } - - /// - /// Gets or sets the response URL. - /// - /// The response URL. - public string ResponseUrl { get; set; } - - /// - /// Gets or sets the content. - /// - /// The content. - public Stream Content { get; set; } - - /// - /// Gets or sets the status code. - /// - /// The status code. - public HttpStatusCode StatusCode { get; set; } - - /// - /// Gets or sets the length of the content. - /// - /// The length of the content. - public long? ContentLength { get; set; } - - /// - /// Gets or sets the headers. - /// - /// The headers. - public Dictionary Headers { get; set; } - - private readonly IDisposable _disposable; - - public HttpResponse(IDisposable disposable) - { - _disposable = disposable; - } - public HttpResponse() - { - } - - public void Dispose() - { - if (_disposable != null) - { - _disposable.Dispose(); - } - } - } -} diff --git a/MediaBrowser.Model/Net/IAcceptSocket.cs b/MediaBrowser.Model/Net/IAcceptSocket.cs deleted file mode 100644 index 2b21d3e660..0000000000 --- a/MediaBrowser.Model/Net/IAcceptSocket.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace MediaBrowser.Model.Net -{ - public class SocketCreateException : Exception - { - public SocketCreateException(string errorCode, Exception originalException) - : base(errorCode, originalException) - { - ErrorCode = errorCode; - } - - public string ErrorCode { get; private set; } - } -} From bf43dc00bb193fb4c7aca882aadbe086ed5a1815 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Wed, 13 Mar 2019 22:32:52 +0100 Subject: [PATCH 156/166] More warning fixes --- .../ApplicationHost.cs | 207 ++++++------ .../Diagnostics/CommonProcess.cs | 72 ++-- .../Library/LibraryManager.cs | 319 +++++++++--------- .../Localization/LocalizationManager.cs | 37 +- .../Session/SessionManager.cs | 242 +++++++------ .../Sorting/AlbumArtistComparer.cs | 2 +- .../Sorting/SeriesSortNameComparer.cs | 14 +- MediaBrowser.Api/BaseApiService.cs | 35 +- .../UserLibrary/ArtistsService.cs | 10 - .../Manager/ProviderManager.cs | 39 +-- .../Music/MusicBrainzAlbumProvider.cs | 24 +- .../Music/MusicBrainzArtistProvider.cs | 18 +- 12 files changed, 532 insertions(+), 487 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 1810064e69..94eaffa479 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -34,7 +34,6 @@ 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.ScheduledTasks; @@ -161,7 +160,7 @@ namespace Emby.Server.Implementations public event EventHandler> ApplicationUpdated; /// - /// Gets or sets a value indicating whether this instance has changes that require the entire application to restart. + /// Gets a value indicating whether this instance has changes that require the entire application to restart. /// /// true if this instance has pending application restart; otherwise, false. public bool HasPendingRestart { get; private set; } @@ -175,7 +174,7 @@ namespace Emby.Server.Implementations protected ILogger Logger { get; set; } /// - /// Gets or sets the plugins. + /// Gets the plugins. /// /// The plugins. public IPlugin[] Plugins { get; protected set; } @@ -187,13 +186,13 @@ namespace Emby.Server.Implementations public ILoggerFactory LoggerFactory { get; protected set; } /// - /// Gets the application paths. + /// Gets or sets the application paths. /// /// The application paths. protected ServerApplicationPaths ApplicationPaths { get; set; } /// - /// Gets all concrete types. + /// Gets or sets all concrete types. /// /// All concrete types. public Type[] AllConcreteTypes { get; protected set; } @@ -201,7 +200,7 @@ namespace Emby.Server.Implementations /// /// The disposable parts /// - protected readonly List DisposableParts = new List(); + protected readonly List _disposableParts = new List(); /// /// Gets the configuration manager. @@ -240,27 +239,33 @@ namespace Emby.Server.Implementations /// /// The user manager. public IUserManager UserManager { get; set; } + /// /// Gets or sets the library manager. /// /// The library manager. internal ILibraryManager LibraryManager { get; set; } + /// /// Gets or sets the directory watchers. /// /// The directory watchers. private ILibraryMonitor LibraryMonitor { get; set; } + /// /// Gets or sets the provider manager. /// /// The provider manager. private IProviderManager ProviderManager { get; set; } + /// /// Gets or sets the HTTP server. /// /// The HTTP server. private IHttpServer HttpServer { get; set; } + private IDtoService DtoService { get; set; } + public IImageProcessor ImageProcessor { get; set; } /// @@ -268,6 +273,7 @@ namespace Emby.Server.Implementations /// /// The media encoder. private IMediaEncoder MediaEncoder { get; set; } + private ISubtitleEncoder SubtitleEncoder { get; set; } private ISessionManager SessionManager { get; set; } @@ -277,6 +283,7 @@ namespace Emby.Server.Implementations public LocalizationManager LocalizationManager { get; set; } private IEncodingManager EncodingManager { get; set; } + private IChannelManager ChannelManager { get; set; } /// @@ -284,20 +291,29 @@ namespace Emby.Server.Implementations /// /// The user data repository. private IUserDataManager UserDataManager { get; set; } + private IUserRepository UserRepository { get; set; } + internal SqliteItemRepository ItemRepository { get; set; } private INotificationManager NotificationManager { get; set; } + private ISubtitleManager SubtitleManager { get; set; } + private IChapterManager ChapterManager { get; set; } + private IDeviceManager DeviceManager { get; set; } internal IUserViewManager UserViewManager { get; set; } private IAuthenticationRepository AuthenticationRepository { get; set; } + private ITVSeriesManager TVSeriesManager { get; set; } + private ICollectionManager CollectionManager { get; set; } + private IMediaSourceManager MediaSourceManager { get; set; } + private IPlaylistManager PlaylistManager { get; set; } private readonly IConfiguration _configuration; @@ -313,28 +329,37 @@ namespace Emby.Server.Implementations /// /// The zip client. protected IZipClient ZipClient { get; private set; } + protected IHttpResultFactory HttpResultFactory { get; private set; } + protected IAuthService AuthService { get; private set; } - public IStartupOptions StartupOptions { get; private set; } + public IStartupOptions StartupOptions { get; } internal IImageEncoder ImageEncoder { get; private set; } protected IProcessFactory ProcessFactory { get; private set; } + protected ICryptoProvider CryptographyProvider = new CryptographyProvider(); protected readonly IXmlSerializer XmlSerializer; protected ISocketFactory SocketFactory { get; private set; } + protected ITaskManager TaskManager { get; private set; } + public IHttpClient HttpClient { get; private set; } + protected INetworkManager NetworkManager { get; set; } + public IJsonSerializer JsonSerializer { get; private set; } + protected IIsoManager IsoManager { get; private set; } /// /// Initializes a new instance of the class. /// - public ApplicationHost(ServerApplicationPaths applicationPaths, + public ApplicationHost( + ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory, IStartupOptions options, IFileSystem fileSystem, @@ -395,7 +420,7 @@ namespace Emby.Server.Implementations _validAddressResults.Clear(); } - public string ApplicationVersion => typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); + public string ApplicationVersion { get; } = typeof(ApplicationHost).Assembly.GetName().Version.ToString(3); /// /// Gets the current application user agent @@ -404,13 +429,16 @@ namespace Emby.Server.Implementations public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; private string _productName; + /// /// Gets the current application name /// /// The application name. - public string ApplicationProductName => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); + public string ApplicationProductName + => _productName ?? (_productName = FileVersionInfo.GetVersionInfo(Assembly.GetEntryAssembly().Location).ProductName); private DeviceId _deviceId; + public string SystemId { get @@ -441,15 +469,15 @@ namespace Emby.Server.Implementations /// /// Creates an instance of type and resolves all constructor dependencies /// - /// The type. - /// System.Object. + /// /// The type + /// T public T CreateInstance() => ActivatorUtilities.CreateInstance(_serviceProvider); /// /// Creates the instance safe. /// - /// The type information. + /// The type. /// System.Object. protected object CreateInstanceSafe(Type type) { @@ -468,14 +496,14 @@ namespace Emby.Server.Implementations /// /// Resolves this instance. /// - /// + /// The type /// ``0. public T Resolve() => _serviceProvider.GetService(); /// /// Gets the export types. /// - /// + /// The type /// IEnumerable{Type}. public IEnumerable GetExportTypes() { @@ -487,22 +515,22 @@ namespace Emby.Server.Implementations /// /// Gets the exports. /// - /// + /// The type /// if set to true [manage lifetime]. /// IEnumerable{``0}. public IEnumerable GetExports(bool manageLifetime = true) { var parts = GetExportTypes() - .Select(x => CreateInstanceSafe(x)) + .Select(CreateInstanceSafe) .Where(i => i != null) .Cast() .ToList(); // Convert to list so this isn't executed for each iteration if (manageLifetime) { - lock (DisposableParts) + lock (_disposableParts) { - DisposableParts.AddRange(parts.OfType()); + _disposableParts.AddRange(parts.OfType()); } } @@ -522,29 +550,20 @@ namespace Emby.Server.Implementations MediaEncoder.SetFFmpegPath(); - //if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath)) - //{ - // if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted) - // { - // ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false; - // ServerConfigurationManager.SaveConfiguration(); - // } - //} - Logger.LogInformation("ServerId: {0}", SystemId); - var entryPoints = GetExports(); + var entryPoints = GetExports().ToList(); var stopWatch = new Stopwatch(); stopWatch.Start(); - await Task.WhenAll(StartEntryPoints(entryPoints, true)); + await Task.WhenAll(StartEntryPoints(entryPoints, true)).ConfigureAwait(false); Logger.LogInformation("Executed all pre-startup entry points in {Elapsed:g}", stopWatch.Elapsed); Logger.LogInformation("Core startup complete"); HttpServer.GlobalResponse = null; stopWatch.Restart(); - await Task.WhenAll(StartEntryPoints(entryPoints, false)); + await Task.WhenAll(StartEntryPoints(entryPoints, false)).ConfigureAwait(false); Logger.LogInformation("Executed all post-startup entry points in {Elapsed:g}", stopWatch.Elapsed); stopWatch.Stop(); } @@ -594,7 +613,7 @@ namespace Emby.Server.Implementations SetHttpLimit(); - await RegisterResources(serviceCollection); + await RegisterResources(serviceCollection).ConfigureAwait(false); FindParts(); @@ -631,7 +650,7 @@ namespace Emby.Server.Implementations }) .Build(); - await host.StartAsync(); + await host.StartAsync().ConfigureAwait(false); } private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) @@ -729,8 +748,8 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ServerConfigurationManager); - LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer, LoggerFactory); - await LocalizationManager.LoadAll(); + LocalizationManager = new LocalizationManager(ServerConfigurationManager, JsonSerializer, LoggerFactory); + await LocalizationManager.LoadAll().ConfigureAwait(false); serviceCollection.AddSingleton(LocalizationManager); serviceCollection.AddSingleton(new BdInfoExaminer(FileSystemManager)); @@ -769,16 +788,19 @@ namespace Emby.Server.Implementations CertificateInfo = GetCertificateInfo(true); Certificate = GetCertificate(CertificateInfo); - HttpServer = new HttpListenerHost(this, + HttpServer = new HttpListenerHost( + this, LoggerFactory, ServerConfigurationManager, _configuration, NetworkManager, JsonSerializer, XmlSerializer, - CreateHttpListener()); + CreateHttpListener()) + { + GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading") + }; - HttpServer.GlobalResponse = LocalizationManager.GetLocalizedString("StartupEmbyServerIsLoading"); serviceCollection.AddSingleton(HttpServer); ImageProcessor = GetImageProcessor(); @@ -933,7 +955,7 @@ namespace Emby.Server.Implementations var password = string.IsNullOrWhiteSpace(info.Password) ? null : info.Password; var localCert = new X509Certificate2(certificateLocation, password); - //localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; + // localCert.PrivateKey = PrivateKey.CreateFromFile(pvk_file).RSA; if (!localCert.HasPrivateKey) { Logger.LogError("No private key included in SSL cert {CertificateLocation}.", certificateLocation); @@ -1034,13 +1056,15 @@ namespace Emby.Server.Implementations HttpServer.Init(GetExports(false), GetExports(), GetUrlPrefixes()); - LibraryManager.AddParts(GetExports(), + LibraryManager.AddParts( + GetExports(), GetExports(), GetExports(), GetExports(), GetExports()); - ProviderManager.AddParts(GetExports(), + ProviderManager.AddParts( + GetExports(), GetExports(), GetExports(), GetExports(), @@ -1121,6 +1145,7 @@ namespace Emby.Server.Implementations } private CertificateInfo CertificateInfo { get; set; } + protected X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() @@ -1131,7 +1156,7 @@ namespace Emby.Server.Implementations { var prefixes = new List { - "http://"+i+":" + HttpPort + "/" + "http://" + i + ":" + HttpPort + "/" }; if (CertificateInfo != null) @@ -1160,30 +1185,12 @@ namespace Emby.Server.Implementations // Generate self-signed cert var certHost = GetHostnameFromExternalDns(ServerConfigurationManager.Configuration.WanDdns); var certPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.ProgramDataPath, "ssl", "cert_" + (certHost + "2").GetMD5().ToString("N") + ".pfx"); - var password = "embycert"; - - //if (generateCertificate) - //{ - // if (!File.Exists(certPath)) - // { - // FileSystemManager.CreateDirectory(FileSystemManager.GetDirectoryName(certPath)); - - // try - // { - // CertificateGenerator.CreateSelfSignCertificatePfx(certPath, certHost, password, Logger); - // } - // catch (Exception ex) - // { - // Logger.LogError(ex, "Error creating ssl cert"); - // return null; - // } - // } - //} + const string Password = "embycert"; return new CertificateInfo { Path = certPath, - Password = password + Password = Password }; } @@ -1218,9 +1225,9 @@ namespace Emby.Server.Implementations requiresRestart = true; } - var currentCertPath = CertificateInfo == null ? null : CertificateInfo.Path; + var currentCertPath = CertificateInfo?.Path; var newCertInfo = GetCertificateInfo(false); - var newCertPath = newCertInfo == null ? null : newCertInfo.Path; + var newCertPath = newCertInfo?.Path; if (!string.Equals(currentCertPath, newCertPath, StringComparison.OrdinalIgnoreCase)) { @@ -1353,6 +1360,7 @@ namespace Emby.Server.Implementations /// /// Gets the system status. /// + /// The cancellation token /// SystemInfo. public async Task GetSystemInfo(CancellationToken cancellationToken) { @@ -1447,19 +1455,19 @@ namespace Emby.Server.Implementations public async Task GetWanApiUrl(CancellationToken cancellationToken) { - const string url = "http://ipv4.icanhazip.com"; + const string Url = "http://ipv4.icanhazip.com"; try { using (var response = await HttpClient.Get(new HttpRequestOptions { - Url = url, + Url = Url, LogErrorResponseBody = false, LogErrors = false, LogRequest = false, TimeoutMs = 10000, BufferContent = false, CancellationToken = cancellationToken - })) + }).ConfigureAwait(false)) { return GetLocalApiUrl(response.ReadToEnd().Trim()); } @@ -1547,10 +1555,12 @@ namespace Emby.Server.Implementations { return result; } + return null; } private readonly ConcurrentDictionary _validAddressResults = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private async Task IsIpAddressValidAsync(IpAddressInfo address, CancellationToken cancellationToken) { if (address.Equals(IpAddressInfo.Loopback) || @@ -1567,26 +1577,26 @@ namespace Emby.Server.Implementations return cachedResult; } - var logPing = false; - #if DEBUG - logPing = true; + const bool LogPing = true; +#else + const bool LogPing = false; #endif try { - using (var response = await HttpClient.SendAsync(new HttpRequestOptions - { - Url = apiUrl, - LogErrorResponseBody = false, - LogErrors = logPing, - LogRequest = logPing, - TimeoutMs = 5000, - BufferContent = false, + using (var response = await HttpClient.SendAsync( + new HttpRequestOptions + { + Url = apiUrl, + LogErrorResponseBody = false, + LogErrors = LogPing, + LogRequest = LogPing, + TimeoutMs = 5000, + BufferContent = false, - CancellationToken = cancellationToken - - }, "POST").ConfigureAwait(false)) + CancellationToken = cancellationToken + }, "POST").ConfigureAwait(false)) { using (var reader = new StreamReader(response.Content)) { @@ -1651,6 +1661,7 @@ namespace Emby.Server.Implementations public event EventHandler HasUpdateAvailableChanged; private bool _hasUpdateAvailable; + public bool HasUpdateAvailable { get => _hasUpdateAvailable; @@ -1711,7 +1722,7 @@ namespace Emby.Server.Implementations var process = ProcessFactory.Create(new ProcessOptions { FileName = url, - //EnableRaisingEvents = true, + EnableRaisingEvents = true, UseShellExecute = true, ErrorDialog = false }); @@ -1746,26 +1757,25 @@ namespace Emby.Server.Implementations { Logger.LogInformation("Application has been updated to version {0}", package.versionStr); - ApplicationUpdated?.Invoke(this, new GenericEventArgs - { - Argument = package - }); + ApplicationUpdated?.Invoke( + this, + new GenericEventArgs() + { + Argument = package + }); NotifyPendingRestart(); } - private bool _disposed; + private bool _disposed = false; + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { - if (!_disposed) - { - _disposed = true; - - Dispose(true); - } + Dispose(true); + GC.SuppressFinalize(this); } /// @@ -1774,14 +1784,19 @@ namespace Emby.Server.Implementations /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool dispose) { + if (_disposed) + { + return; + } + if (dispose) { var type = GetType(); Logger.LogInformation("Disposing {Type}", type.Name); - var parts = DisposableParts.Distinct().Where(i => i.GetType() != type).ToList(); - DisposableParts.Clear(); + var parts = _disposableParts.Distinct().Where(i => i.GetType() != type).ToList(); + _disposableParts.Clear(); foreach (var part in parts) { @@ -1797,6 +1812,8 @@ namespace Emby.Server.Implementations } } } + + _disposed = true; } } diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 2c4ef170dc..175a8f3ce4 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -9,14 +9,14 @@ namespace Emby.Server.Implementations.Diagnostics { public class CommonProcess : IProcess { - public event EventHandler Exited; - - private readonly ProcessOptions _options; private readonly Process _process; + private bool _disposed = false; + private bool _hasExited; + public CommonProcess(ProcessOptions options) { - _options = options; + StartInfo = options; var startInfo = new ProcessStartInfo { @@ -27,10 +27,10 @@ namespace Emby.Server.Implementations.Diagnostics CreateNoWindow = options.CreateNoWindow, RedirectStandardError = options.RedirectStandardError, RedirectStandardInput = options.RedirectStandardInput, - RedirectStandardOutput = options.RedirectStandardOutput + RedirectStandardOutput = options.RedirectStandardOutput, + ErrorDialog = options.ErrorDialog }; - startInfo.ErrorDialog = options.ErrorDialog; if (options.IsHidden) { @@ -45,11 +45,22 @@ namespace Emby.Server.Implementations.Diagnostics if (options.EnableRaisingEvents) { _process.EnableRaisingEvents = true; - _process.Exited += _process_Exited; + _process.Exited += OnProcessExited; } } - private bool _hasExited; + public event EventHandler Exited; + + public ProcessOptions StartInfo { get; } + + public StreamWriter StandardInput => _process.StandardInput; + + public StreamReader StandardError => _process.StandardError; + + public StreamReader StandardOutput => _process.StandardOutput; + + public int ExitCode => _process.ExitCode; + private bool HasExited { get @@ -72,25 +83,6 @@ namespace Emby.Server.Implementations.Diagnostics } } - private void _process_Exited(object sender, EventArgs e) - { - _hasExited = true; - if (Exited != null) - { - Exited(this, e); - } - } - - public ProcessOptions StartInfo => _options; - - public StreamWriter StandardInput => _process.StandardInput; - - public StreamReader StandardError => _process.StandardError; - - public StreamReader StandardOutput => _process.StandardOutput; - - public int ExitCode => _process.ExitCode; - public void Start() { _process.Start(); @@ -108,7 +100,7 @@ namespace Emby.Server.Implementations.Diagnostics public Task WaitForExitAsync(int timeMs) { - //Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. + // Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. if (HasExited) { @@ -130,7 +122,29 @@ namespace Emby.Server.Implementations.Diagnostics public void Dispose() { - _process?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + _process?.Dispose(); + } + + _disposed = true; + } + + private void OnProcessExited(object sender, EventArgs e) + { + _hasExited = true; + Exited?.Invoke(this, e); } } } diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 6591d54c56..1673e37776 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -58,22 +58,23 @@ namespace Emby.Server.Implementations.Library private ILibraryPostScanTask[] PostscanTasks { get; set; } /// - /// Gets the intro providers. + /// Gets or sets the intro providers. /// /// The intro providers. private IIntroProvider[] IntroProviders { get; set; } /// - /// Gets the list of entity resolution ignore rules + /// Gets or sets the list of entity resolution ignore rules /// /// The entity resolution ignore rules. private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; } /// - /// Gets the list of currently registered entity resolvers + /// Gets or sets the list of currently registered entity resolvers /// /// The entity resolvers enumerable. private IItemResolver[] EntityResolvers { get; set; } + private IMultiItemResolver[] MultiItemResolvers { get; set; } /// @@ -83,7 +84,7 @@ namespace Emby.Server.Implementations.Library private IBaseItemComparer[] Comparers { get; set; } /// - /// Gets the active item repository + /// Gets or sets the active item repository /// /// The item repository. public IItemRepository ItemRepository { get; set; } @@ -133,12 +134,14 @@ namespace Emby.Server.Implementations.Library private readonly Func _providerManagerFactory; private readonly Func _userviewManager; public bool IsScanRunning { get; private set; } + private IServerApplicationHost _appHost; /// /// The _library items cache /// private readonly ConcurrentDictionary _libraryItemsCache; + /// /// Gets the library items cache. /// @@ -150,7 +153,8 @@ namespace Emby.Server.Implementations.Library /// /// Initializes a new instance of the class. /// - /// The logger. + /// The application host + /// The logger factory. /// The task manager. /// The user manager. /// The configuration manager. @@ -167,6 +171,7 @@ namespace Emby.Server.Implementations.Library Func providerManagerFactory, Func userviewManager) { + _appHost = appHost; _logger = loggerFactory.CreateLogger(nameof(LibraryManager)); _taskManager = taskManager; _userManager = userManager; @@ -176,7 +181,7 @@ namespace Emby.Server.Implementations.Library _fileSystem = fileSystem; _providerManagerFactory = providerManagerFactory; _userviewManager = userviewManager; - _appHost = appHost; + _libraryItemsCache = new ConcurrentDictionary(); ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated; @@ -191,8 +196,9 @@ namespace Emby.Server.Implementations.Library /// The resolvers. /// The intro providers. /// The item comparers. - /// The postscan tasks. - public void AddParts(IEnumerable rules, + /// The post scan tasks. + public void AddParts( + IEnumerable rules, IEnumerable resolvers, IEnumerable introProviders, IEnumerable itemComparers, @@ -203,24 +209,19 @@ namespace Emby.Server.Implementations.Library MultiItemResolvers = EntityResolvers.OfType().ToArray(); IntroProviders = introProviders.ToArray(); Comparers = itemComparers.ToArray(); - - PostscanTasks = postscanTasks.OrderBy(i => - { - var hasOrder = i as IHasOrder; - - return hasOrder == null ? 0 : hasOrder.Order; - - }).ToArray(); + PostscanTasks = postscanTasks.ToArray(); } /// /// The _root folder /// private volatile AggregateFolder _rootFolder; + /// /// The _root folder sync lock /// private readonly object _rootFolderSyncLock = new object(); + /// /// Gets the root folder. /// @@ -239,11 +240,13 @@ namespace Emby.Server.Implementations.Library } } } + return _rootFolder; } } private bool _wizardCompleted; + /// /// Records the configuration values. /// @@ -258,7 +261,7 @@ namespace Emby.Server.Implementations.Library /// /// The sender. /// The instance containing the event data. - void ConfigurationUpdated(object sender, EventArgs e) + private void ConfigurationUpdated(object sender, EventArgs e) { var config = ConfigurationManager.Configuration; @@ -335,12 +338,14 @@ namespace Emby.Server.Implementations.Library // channel no longer installed } } + options.DeleteFileLocation = false; } if (item is LiveTvProgram) { - _logger.LogDebug("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + _logger.LogDebug( + "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -348,7 +353,8 @@ namespace Emby.Server.Implementations.Library } else { - _logger.LogInformation("Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", + _logger.LogInformation( + "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", item.GetType().Name, item.Name ?? "Unknown name", item.Path ?? string.Empty, @@ -488,12 +494,13 @@ namespace Emby.Server.Implementations.Library { throw new ArgumentNullException(nameof(key)); } + if (type == null) { throw new ArgumentNullException(nameof(type)); } - if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath)) + if (key.StartsWith(ConfigurationManager.ApplicationPaths.ProgramDataPath, StringComparison.Ordinal)) { // Try to normalize paths located underneath program-data in an attempt to make them more portable key = key.Substring(ConfigurationManager.ApplicationPaths.ProgramDataPath.Length) @@ -511,13 +518,11 @@ namespace Emby.Server.Implementations.Library return key.GetMD5(); } - public BaseItem ResolvePath(FileSystemMetadata fileInfo, - Folder parent = null) - { - return ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); - } + public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null) + => ResolvePath(fileInfo, new DirectoryService(_logger, _fileSystem), null, parent); - private BaseItem ResolvePath(FileSystemMetadata fileInfo, + private BaseItem ResolvePath( + FileSystemMetadata fileInfo, IDirectoryService directoryService, IItemResolver[] resolvers, Folder parent = null, @@ -572,7 +577,7 @@ namespace Emby.Server.Implementations.Library { _logger.LogError(ex, "Error in GetFilteredFileSystemEntries isPhysicalRoot: {0} IsVf: {1}", isPhysicalRoot, isVf); - files = new FileSystemMetadata[] { }; + files = Array.Empty(); } else { @@ -600,13 +605,7 @@ namespace Emby.Server.Implementations.Library } public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) - { - if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent))) - { - return true; - } - return false; - } + => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)); public List NormalizeRootPathList(IEnumerable paths) { @@ -646,7 +645,8 @@ namespace Emby.Server.Implementations.Library return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers); } - public IEnumerable ResolvePaths(IEnumerable files, + public IEnumerable ResolvePaths( + IEnumerable files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, @@ -672,6 +672,7 @@ namespace Emby.Server.Implementations.Library { ResolverHelper.SetInitialItemValues(item, parent, _fileSystem, this, directoryService); } + items.AddRange(ResolveFileList(result.ExtraFiles, directoryService, parent, collectionType, resolvers, libraryOptions)); return items; } @@ -681,7 +682,8 @@ namespace Emby.Server.Implementations.Library return ResolveFileList(fileList, directoryService, parent, collectionType, resolvers, libraryOptions); } - private IEnumerable ResolveFileList(IEnumerable fileList, + private IEnumerable ResolveFileList( + IEnumerable fileList, IDirectoryService directoryService, Folder parent, string collectionType, @@ -766,6 +768,7 @@ namespace Emby.Server.Implementations.Library private volatile UserRootFolder _userRootFolder; private readonly object _syncLock = new object(); + public Folder GetUserRootFolder() { if (_userRootFolder == null) @@ -810,8 +813,6 @@ namespace Emby.Server.Implementations.Library throw new ArgumentNullException(nameof(path)); } - //_logger.LogInformation("FindByPath {0}", path); - var query = new InternalItemsQuery { Path = path, @@ -885,7 +886,6 @@ namespace Emby.Server.Implementations.Library /// /// The value. /// Task{Year}. - /// public Year GetYear(int value) { if (value <= 0) @@ -1027,20 +1027,25 @@ namespace Emby.Server.Implementations.Library private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) { - var rootChildren = RootFolder.Children.ToList(); - rootChildren = GetUserRootFolder().Children.ToList(); - await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); // Start by just validating the children of the root, but go no further - await RootFolder.ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false); + await RootFolder.ValidateChildren( + new SimpleProgress(), + cancellationToken, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + recursive: false).ConfigureAwait(false); await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); - await GetUserRootFolder().ValidateChildren(new SimpleProgress(), cancellationToken, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), recursive: false).ConfigureAwait(false); + await GetUserRootFolder().ValidateChildren( + new SimpleProgress(), + cancellationToken, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)), + recursive: false).ConfigureAwait(false); // Quickly scan CollectionFolders for changes - foreach (var folder in GetUserRootFolder().Children.OfType().ToList()) + foreach (var folder in GetUserRootFolder().Children.OfType()) { await folder.RefreshMetadata(cancellationToken).ConfigureAwait(false); } @@ -1204,7 +1209,7 @@ namespace Emby.Server.Implementations.Library private string GetCollectionType(string path) { return _fileSystem.GetFilePaths(path, new[] { ".collection" }, true, false) - .Select(i => Path.GetFileNameWithoutExtension(i)) + .Select(Path.GetFileNameWithoutExtension) .FirstOrDefault(i => !string.IsNullOrEmpty(i)); } @@ -1218,7 +1223,7 @@ namespace Emby.Server.Implementations.Library { if (id == Guid.Empty) { - throw new ArgumentException(nameof(id), "Guid can't be empty"); + throw new ArgumentException("Guid can't be empty", nameof(id)); } if (LibraryItemsCache.TryGetValue(id, out BaseItem item)) @@ -1386,17 +1391,7 @@ namespace Emby.Server.Implementations.Library var parents = query.AncestorIds.Select(i => GetItemById(i)).ToList(); - if (parents.All(i => - { - if (i is ICollectionFolder || i is UserView) - { - return true; - } - - //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name); - return false; - - })) + if (parents.All(i => i is ICollectionFolder || i is UserView)) { // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); @@ -1452,17 +1447,7 @@ namespace Emby.Server.Implementations.Library private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List parents) { - if (parents.All(i => - { - if (i is ICollectionFolder || i is UserView) - { - return true; - } - - //_logger.LogDebug("Query requires ancestor query due to type: " + i.GetType().Name); - return false; - - })) + if (parents.All(i => i is ICollectionFolder || i is UserView)) { // Optimize by querying against top level views query.TopParentIds = parents.SelectMany(i => GetTopParentIdsForQuery(i, query.User)).ToArray(); @@ -1511,11 +1496,9 @@ namespace Emby.Server.Implementations.Library private IEnumerable GetTopParentIdsForQuery(BaseItem item, User user) { - var view = item as UserView; - - if (view != null) + if (item is UserView view) { - if (string.Equals(view.ViewType, CollectionType.LiveTv)) + if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal)) { return new[] { view.Id }; } @@ -1528,8 +1511,10 @@ namespace Emby.Server.Implementations.Library { return GetTopParentIdsForQuery(displayParent, user); } + return Array.Empty(); } + if (!view.ParentId.Equals(Guid.Empty)) { var displayParent = GetItemById(view.ParentId); @@ -1537,6 +1522,7 @@ namespace Emby.Server.Implementations.Library { return GetTopParentIdsForQuery(displayParent, user); } + return Array.Empty(); } @@ -1550,11 +1536,11 @@ namespace Emby.Server.Implementations.Library .Where(i => user.IsFolderGrouped(i.Id)) .SelectMany(i => GetTopParentIdsForQuery(i, user)); } + return Array.Empty(); } - var collectionFolder = item as CollectionFolder; - if (collectionFolder != null) + if (item is CollectionFolder collectionFolder) { return collectionFolder.PhysicalFolderIds; } @@ -1564,6 +1550,7 @@ namespace Emby.Server.Implementations.Library { return new[] { topParent.Id }; } + return Array.Empty(); } @@ -1760,19 +1747,16 @@ namespace Emby.Server.Implementations.Library { var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase)); - if (comparer != null) + // If it requires a user, create a new one, and assign the user + if (comparer is IUserBaseItemComparer) { - // If it requires a user, create a new one, and assign the user - if (comparer is IUserBaseItemComparer) - { - var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); + var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); - userComparer.User = user; - userComparer.UserManager = _userManager; - userComparer.UserDataRepository = _userDataRepository; + userComparer.User = user; + userComparer.UserManager = _userManager; + userComparer.UserDataRepository = _userDataRepository; - return userComparer; - } + return userComparer; } return comparer; @@ -1783,7 +1767,6 @@ namespace Emby.Server.Implementations.Library /// /// The item. /// The parent item. - /// Task. public void CreateItem(BaseItem item, BaseItem parent) { CreateItems(new[] { item }, parent, CancellationToken.None); @@ -1793,20 +1776,23 @@ namespace Emby.Server.Implementations.Library /// Creates the items. /// /// The items. + /// The parent item /// The cancellation token. - /// Task. public void CreateItems(IEnumerable items, BaseItem parent, CancellationToken cancellationToken) { - ItemRepository.SaveItems(items, cancellationToken); + // Don't iterate multiple times + var itemsList = items.ToList(); - foreach (var item in items) + ItemRepository.SaveItems(itemsList, cancellationToken); + + foreach (var item in itemsList) { RegisterItem(item); } if (ItemAdded != null) { - foreach (var item in items) + foreach (var item in itemsList) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1816,11 +1802,13 @@ namespace Emby.Server.Implementations.Library try { - ItemAdded(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent ?? item.GetParent() - }); + ItemAdded( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent ?? item.GetParent() + }); } catch (Exception ex) { @@ -1842,7 +1830,10 @@ namespace Emby.Server.Implementations.Library /// public void UpdateItems(IEnumerable items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { - foreach (var item in items) + // Don't iterate multiple times + var itemsList = items.ToList(); + + foreach (var item in itemsList) { if (item.IsFileProtocol) { @@ -1854,14 +1845,11 @@ namespace Emby.Server.Implementations.Library RegisterItem(item); } - //var logName = item.LocationType == LocationType.Remote ? item.Name ?? item.Path : item.Path ?? item.Name; - //_logger.LogDebug("Saving {0} to database.", logName); - - ItemRepository.SaveItems(items, cancellationToken); + ItemRepository.SaveItems(itemsList, cancellationToken); if (ItemUpdated != null) { - foreach (var item in items) + foreach (var item in itemsList) { // With the live tv guide this just creates too much noise if (item.SourceType != SourceType.Library) @@ -1871,12 +1859,14 @@ namespace Emby.Server.Implementations.Library try { - ItemUpdated(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent, - UpdateReason = updateReason - }); + ItemUpdated( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent, + UpdateReason = updateReason + }); } catch (Exception ex) { @@ -1890,9 +1880,9 @@ namespace Emby.Server.Implementations.Library /// Updates the item. /// /// The item. + /// The parent item. /// The update reason. /// The cancellation token. - /// Task. public void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken) { UpdateItems(new [] { item }, parent, updateReason, cancellationToken); @@ -1902,17 +1892,20 @@ namespace Emby.Server.Implementations.Library /// Reports the item removed. /// /// The item. + /// The parent item. public void ReportItemRemoved(BaseItem item, BaseItem parent) { if (ItemRemoved != null) { try { - ItemRemoved(this, new ItemChangeEventArgs - { - Item = item, - Parent = parent - }); + ItemRemoved( + this, + new ItemChangeEventArgs + { + Item = item, + Parent = parent + }); } catch (Exception ex) { @@ -2038,8 +2031,7 @@ namespace Emby.Server.Implementations.Library public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath) { - var collectionFolder = item as ICollectionFolder; - if (collectionFolder != null) + if (item is ICollectionFolder collectionFolder) { return collectionFolder.CollectionType; } @@ -2049,13 +2041,11 @@ namespace Emby.Server.Implementations.Library private string GetContentTypeOverride(string path, bool inherit) { - var nameValuePair = ConfigurationManager.Configuration.ContentTypes.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) || (inherit && !string.IsNullOrEmpty(i.Name) && _fileSystem.ContainsSubPath(i.Name, path))); - if (nameValuePair != null) - { - return nameValuePair.Value; - } - - return null; + var nameValuePair = ConfigurationManager.Configuration.ContentTypes + .FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path) + || (inherit && !string.IsNullOrEmpty(i.Name) + && _fileSystem.ContainsSubPath(i.Name, path))); + return nameValuePair?.Value; } private string GetTopFolderContentType(BaseItem item) @@ -2072,6 +2062,7 @@ namespace Emby.Server.Implementations.Library { break; } + item = parent; } @@ -2083,9 +2074,9 @@ namespace Emby.Server.Implementations.Library } private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24); - //private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromMinutes(1); - public UserView GetNamedView(User user, + public UserView GetNamedView( + User user, string name, string viewType, string sortName) @@ -2093,13 +2084,15 @@ namespace Emby.Server.Implementations.Library return GetNamedView(user, name, Guid.Empty, viewType, sortName); } - public UserView GetNamedView(string name, + public UserView GetNamedView( + string name, string viewType, string sortName) { - var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, - "views", - _fileSystem.GetValidFilename(viewType)); + var path = Path.Combine( + ConfigurationManager.ApplicationPaths.InternalMetadataPath, + "views", + _fileSystem.GetValidFilename(viewType)); var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView)); @@ -2135,7 +2128,8 @@ namespace Emby.Server.Implementations.Library return item; } - public UserView GetNamedView(User user, + public UserView GetNamedView( + User user, string name, Guid parentId, string viewType, @@ -2164,10 +2158,10 @@ namespace Emby.Server.Implementations.Library Name = name, ViewType = viewType, ForcedSortName = sortName, - UserId = user.Id + UserId = user.Id, + DisplayParentId = parentId }; - item.DisplayParentId = parentId; CreateItem(item, null); @@ -2184,20 +2178,24 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true - }, RefreshPriority.Normal); + }, + RefreshPriority.Normal); } return item; } - public UserView GetShadowView(BaseItem parent, - string viewType, - string sortName) + public UserView GetShadowView( + BaseItem parent, + string viewType, + string sortName) { if (parent == null) { @@ -2248,18 +2246,21 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true - - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); } return item; } - public UserView GetNamedView(string name, + public UserView GetNamedView( + string name, Guid parentId, string viewType, string sortName, @@ -2322,17 +2323,21 @@ namespace Emby.Server.Implementations.Library if (refresh) { - _providerManagerFactory().QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) - { - // Need to force save to increment DateLastSaved - ForceSave = true - }, RefreshPriority.Normal); + _providerManagerFactory().QueueRefresh( + item.Id, + new MetadataRefreshOptions(new DirectoryService(_logger, _fileSystem)) + { + // Need to force save to increment DateLastSaved + ForceSave = true + }, + RefreshPriority.Normal); } return item; } - public void AddExternalSubtitleStreams(List streams, + public void AddExternalSubtitleStreams( + List streams, string videoPath, string[] files) { @@ -2436,6 +2441,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.IndexNumber = episodeInfo.EpisodeNumber; } @@ -2445,6 +2451,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.IndexNumberEnd = episodeInfo.EndingEpsiodeNumber; } @@ -2454,6 +2461,7 @@ namespace Emby.Server.Implementations.Library { changed = true; } + episode.ParentIndexNumber = episodeInfo.SeasonNumber; } } @@ -2483,6 +2491,7 @@ namespace Emby.Server.Implementations.Library private NamingOptions _namingOptions; private string[] _videoFileExtensions; + private NamingOptions GetNamingOptionsInternal() { if (_namingOptions == null) @@ -2679,7 +2688,7 @@ namespace Emby.Server.Implementations.Library var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase); var changed = false; - if (!string.Equals(newPath, path)) + if (!string.Equals(newPath, path, StringComparison.Ordinal)) { if (to.IndexOf('/') != -1) { @@ -2803,6 +2812,7 @@ namespace Emby.Server.Implementations.Library { continue; } + throw; } } @@ -2907,6 +2917,7 @@ namespace Emby.Server.Implementations.Library } private const string ShortcutFileExtension = ".mblink"; + public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo) { AddMediaPathInternal(virtualFolderName, pathInfo, true); @@ -2923,7 +2934,7 @@ namespace Emby.Server.Implementations.Library if (string.IsNullOrWhiteSpace(path)) { - throw new ArgumentNullException(nameof(path)); + throw new ArgumentException(nameof(path)); } if (!Directory.Exists(path)) diff --git a/Emby.Server.Implementations/Localization/LocalizationManager.cs b/Emby.Server.Implementations/Localization/LocalizationManager.cs index 762649b716..8c49b64055 100644 --- a/Emby.Server.Implementations/Localization/LocalizationManager.cs +++ b/Emby.Server.Implementations/Localization/LocalizationManager.cs @@ -11,7 +11,6 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Globalization; -using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; @@ -35,7 +34,6 @@ namespace Emby.Server.Implementations.Localization private readonly Dictionary> _allParentalRatings = new Dictionary>(StringComparer.OrdinalIgnoreCase); - private readonly IFileSystem _fileSystem; private readonly IJsonSerializer _jsonSerializer; private readonly ILogger _logger; private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly; @@ -44,40 +42,38 @@ namespace Emby.Server.Implementations.Localization /// Initializes a new instance of the class. /// /// The configuration manager. - /// The file system. /// The json serializer. + /// The logger factory public LocalizationManager( IServerConfigurationManager configurationManager, - IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILoggerFactory loggerFactory) { _configurationManager = configurationManager; - _fileSystem = fileSystem; _jsonSerializer = jsonSerializer; _logger = loggerFactory.CreateLogger(nameof(LocalizationManager)); } public async Task LoadAll() { - const string ratingsResource = "Emby.Server.Implementations.Localization.Ratings."; + const string RatingsResource = "Emby.Server.Implementations.Localization.Ratings."; // Extract from the assembly foreach (var resource in _assembly.GetManifestResourceNames()) { - if (!resource.StartsWith(ratingsResource)) + if (!resource.StartsWith(RatingsResource, StringComparison.Ordinal)) { continue; } - string countryCode = resource.Substring(ratingsResource.Length, 2); + string countryCode = resource.Substring(RatingsResource.Length, 2); var dict = new Dictionary(StringComparer.OrdinalIgnoreCase); using (var str = _assembly.GetManifestResourceStream(resource)) using (var reader = new StreamReader(str)) { string line; - while ((line = await reader.ReadLineAsync()) != null) + while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null) { if (string.IsNullOrWhiteSpace(line)) { @@ -102,7 +98,7 @@ namespace Emby.Server.Implementations.Localization _allParentalRatings[countryCode] = dict; } - await LoadCultures(); + await LoadCultures().ConfigureAwait(false); } public string NormalizeFormKD(string text) @@ -121,14 +117,14 @@ namespace Emby.Server.Implementations.Localization { List list = new List(); - const string path = "Emby.Server.Implementations.Localization.iso6392.txt"; + const string ResourcePath = "Emby.Server.Implementations.Localization.iso6392.txt"; - using (var stream = _assembly.GetManifestResourceStream(path)) + using (var stream = _assembly.GetManifestResourceStream(ResourcePath)) using (var reader = new StreamReader(stream)) { while (!reader.EndOfStream) { - var line = await reader.ReadLineAsync(); + var line = await reader.ReadLineAsync().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(line)) { @@ -154,11 +150,11 @@ namespace Emby.Server.Implementations.Localization string[] threeletterNames; if (string.IsNullOrWhiteSpace(parts[1])) { - threeletterNames = new [] { parts[0] }; + threeletterNames = new[] { parts[0] }; } else { - threeletterNames = new [] { parts[0], parts[1] }; + threeletterNames = new[] { parts[0], parts[1] }; } list.Add(new CultureDto @@ -218,6 +214,7 @@ namespace Emby.Server.Implementations.Localization /// Gets the ratings. /// /// The country code. + /// The ratings private Dictionary GetRatings(string countryCode) { _allParentalRatings.TryGetValue(countryCode, out var value); @@ -227,9 +224,12 @@ namespace Emby.Server.Implementations.Localization private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" }; + /// /// /// Gets the rating level. /// + /// Rating field + /// The rating level> public int? GetRatingLevel(string rating) { if (string.IsNullOrEmpty(rating)) @@ -301,6 +301,7 @@ namespace Emby.Server.Implementations.Localization { culture = _configurationManager.Configuration.UICulture; } + if (string.IsNullOrEmpty(culture)) { culture = DefaultCulture; @@ -346,8 +347,8 @@ namespace Emby.Server.Implementations.Localization var namespaceName = GetType().Namespace + "." + prefix; - await CopyInto(dictionary, namespaceName + "." + baseFilename); - await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)); + await CopyInto(dictionary, namespaceName + "." + baseFilename).ConfigureAwait(false); + await CopyInto(dictionary, namespaceName + "." + GetResourceFilename(culture)).ConfigureAwait(false); return dictionary; } @@ -359,7 +360,7 @@ namespace Emby.Server.Implementations.Localization // If a Culture doesn't have a translation the stream will be null and it defaults to en-us further up the chain if (stream != null) { - var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream); + var dict = await _jsonSerializer.DeserializeFromStreamAsync>(stream).ConfigureAwait(false); foreach (var key in dict.Keys) { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 03e7b26545..985748caf2 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -116,14 +116,14 @@ namespace Emby.Server.Implementations.Session _authRepo = authRepo; _deviceManager = deviceManager; _mediaSourceManager = mediaSourceManager; - _deviceManager.DeviceOptionsUpdated += _deviceManager_DeviceOptionsUpdated; + _deviceManager.DeviceOptionsUpdated += OnDeviceManagerDeviceOptionsUpdated; } - private void _deviceManager_DeviceOptionsUpdated(object sender, GenericEventArgs> e) + private void OnDeviceManagerDeviceOptionsUpdated(object sender, GenericEventArgs> e) { foreach (var session in Sessions) { - if (string.Equals(session.DeviceId, e.Argument.Item1)) + if (string.Equals(session.DeviceId, e.Argument.Item1, StringComparison.Ordinal)) { if (!string.IsNullOrWhiteSpace(e.Argument.Item2.CustomName)) { @@ -138,11 +138,29 @@ namespace Emby.Server.Implementations.Session } } - private bool _disposed; + private bool _disposed = false; + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + // TODO: dispose stuff + } + + _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; + _disposed = true; - _deviceManager.DeviceOptionsUpdated -= _deviceManager_DeviceOptionsUpdated; } public void CheckDisposed() @@ -157,7 +175,7 @@ namespace Emby.Server.Implementations.Session /// Gets all connections. /// /// All connections. - public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate).ToList(); + public IEnumerable Sessions => _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); private void OnSessionStarted(SessionInfo info) { @@ -171,20 +189,27 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull(SessionStarted, this, new SessionEventArgs - { - SessionInfo = info - - }, _logger); + EventHelper.QueueEventIfNotNull( + SessionStarted, + this, + new SessionEventArgs + { + SessionInfo = info + }, + _logger); } private void OnSessionEnded(SessionInfo info) { - EventHelper.QueueEventIfNotNull(SessionEnded, this, new SessionEventArgs - { - SessionInfo = info + EventHelper.QueueEventIfNotNull( + SessionEnded, + this, + new SessionEventArgs + { + SessionInfo = info - }, _logger); + }, + _logger); info.Dispose(); } @@ -192,9 +217,6 @@ namespace Emby.Server.Implementations.Session public void UpdateDeviceName(string sessionId, string deviceName) { var session = GetSession(sessionId); - - var key = GetSessionKey(session.Client, session.DeviceId); - if (session != null) { session.DeviceName = deviceName; @@ -210,10 +232,10 @@ namespace Emby.Server.Implementations.Session /// Name of the device. /// The remote end point. /// The user. - /// Task. + /// SessionInfo. /// user - /// - public SessionInfo LogSessionActivity(string appName, + public SessionInfo LogSessionActivity( + string appName, string appVersion, string deviceId, string deviceName, @@ -226,10 +248,12 @@ namespace Emby.Server.Implementations.Session { throw new ArgumentNullException(nameof(appName)); } + if (string.IsNullOrEmpty(appVersion)) { throw new ArgumentNullException(nameof(appVersion)); } + if (string.IsNullOrEmpty(deviceId)) { throw new ArgumentNullException(nameof(deviceId)); @@ -260,10 +284,12 @@ namespace Emby.Server.Implementations.Session if ((activityDate - lastActivityDate).TotalSeconds > 10) { - SessionActivity?.Invoke(this, new SessionEventArgs - { - SessionInfo = session - }); + SessionActivity?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); } return session; @@ -304,6 +330,7 @@ namespace Emby.Server.Implementations.Session /// /// Updates the now playing item id. /// + /// Task. private async Task UpdateNowPlayingItem(SessionInfo session, PlaybackProgressInfo info, BaseItem libraryItem, bool updateLastCheckInTime) { if (string.IsNullOrEmpty(info.MediaSourceId)) @@ -418,7 +445,7 @@ namespace Emby.Server.Implementations.Session }); sessionInfo.UserId = user == null ? Guid.Empty : user.Id; - sessionInfo.UserName = user == null ? null : user.Name; + sessionInfo.UserName = user?.Name; sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); sessionInfo.RemoteEndPoint = remoteEndPoint; sessionInfo.Client = appName; @@ -432,7 +459,7 @@ namespace Emby.Server.Implementations.Session if (user == null) { - sessionInfo.AdditionalUsers = new SessionUserInfo[] { }; + sessionInfo.AdditionalUsers = Array.Empty(); } return sessionInfo; @@ -449,9 +476,9 @@ namespace Emby.Server.Implementations.Session ServerId = _appHost.SystemId }; - var username = user == null ? null : user.Name; + var username = user?.Name; - sessionInfo.UserId = user == null ? Guid.Empty : user.Id; + sessionInfo.UserId = user?.Id ?? Guid.Empty; sessionInfo.UserName = username; sessionInfo.UserPrimaryImageTag = user == null ? null : GetImageCacheTag(user, ImageType.Primary); sessionInfo.RemoteEndPoint = remoteEndPoint; @@ -508,6 +535,7 @@ namespace Emby.Server.Implementations.Session _idleTimer = new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); } } + private void StopIdleCheckTimer() { if (_idleTimer != null) @@ -539,9 +567,9 @@ namespace Emby.Server.Implementations.Session Item = session.NowPlayingItem, ItemId = session.NowPlayingItem == null ? Guid.Empty : session.NowPlayingItem.Id, SessionId = session.Id, - MediaSourceId = session.PlayState == null ? null : session.PlayState.MediaSourceId, - PositionTicks = session.PlayState == null ? null : session.PlayState.PositionTicks - }); + MediaSourceId = session.PlayState?.MediaSourceId, + PositionTicks = session.PlayState?.PositionTicks + }).ConfigureAwait(false); } catch (Exception ex) { @@ -616,18 +644,22 @@ namespace Emby.Server.Implementations.Session // Nothing to save here // Fire events to inform plugins - EventHelper.QueueEventIfNotNull(PlaybackStart, this, new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session + EventHelper.QueueEventIfNotNull( + PlaybackStart, + this, + new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session - }, _logger); + }, + _logger); StartIdleCheckTimer(); } @@ -667,6 +699,7 @@ namespace Emby.Server.Implementations.Session /// /// Used to report playback progress for an item /// + /// Task. public async Task OnPlaybackProgress(PlaybackProgressInfo info, bool isAutomated) { CheckDisposed(); @@ -695,21 +728,23 @@ namespace Emby.Server.Implementations.Session } } - PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = session.PlayState.PositionTicks, - MediaSourceId = session.PlayState.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - IsPaused = info.IsPaused, - PlaySessionId = info.PlaySessionId, - IsAutomated = isAutomated, - Session = session - }); + PlaybackProgress?.Invoke( + this, + new PlaybackProgressEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = session.PlayState.PositionTicks, + MediaSourceId = session.PlayState.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + IsPaused = info.IsPaused, + PlaySessionId = info.PlaySessionId, + IsAutomated = isAutomated, + Session = session + }); if (!isAutomated) { @@ -830,8 +865,7 @@ namespace Emby.Server.Implementations.Session { MediaSourceInfo mediaSource = null; - var hasMediaSources = libraryItem as IHasMediaSources; - if (hasMediaSources != null) + if (libraryItem is IHasMediaSources hasMediaSources) { mediaSource = await GetMediaSource(libraryItem, info.MediaSourceId, info.LiveStreamId).ConfigureAwait(false); } @@ -848,7 +882,8 @@ namespace Emby.Server.Implementations.Session { var msString = info.PositionTicks.HasValue ? (info.PositionTicks.Value / 10000).ToString(CultureInfo.InvariantCulture) : "unknown"; - _logger.LogInformation("Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms", + _logger.LogInformation( + "Playback stopped reported by app {0} {1} playing {2}. Stopped at {3} ms", session.Client, session.ApplicationVersion, info.Item.Name, @@ -887,20 +922,24 @@ namespace Emby.Server.Implementations.Session } } - EventHelper.QueueEventIfNotNull(PlaybackStopped, this, new PlaybackStopEventArgs - { - Item = libraryItem, - Users = users, - PlaybackPositionTicks = info.PositionTicks, - PlayedToCompletion = playedToCompletion, - MediaSourceId = info.MediaSourceId, - MediaInfo = info.Item, - DeviceName = session.DeviceName, - ClientName = session.Client, - DeviceId = session.DeviceId, - Session = session + EventHelper.QueueEventIfNotNull( + PlaybackStopped, + this, + new PlaybackStopEventArgs + { + Item = libraryItem, + Users = users, + PlaybackPositionTicks = info.PositionTicks, + PlayedToCompletion = playedToCompletion, + MediaSourceId = info.MediaSourceId, + MediaInfo = info.Item, + DeviceName = session.DeviceName, + ClientName = session.Client, + DeviceId = session.DeviceId, + Session = session - }, _logger); + }, + _logger); } private bool OnPlaybackStopped(User user, BaseItem item, long? positionTicks, bool playbackFailed) @@ -936,11 +975,10 @@ namespace Emby.Server.Implementations.Session /// The session identifier. /// if set to true [throw on missing]. /// SessionInfo. - /// + /// sessionId private SessionInfo GetSession(string sessionId, bool throwOnMissing = true) { - var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); - + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null && throwOnMissing) { throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId)); @@ -952,7 +990,7 @@ namespace Emby.Server.Implementations.Session private SessionInfo GetSessionToRemoteControl(string sessionId) { // Accept either device id or session id - var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId)); + var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId, StringComparison.Ordinal)); if (session == null) { @@ -1061,10 +1099,12 @@ namespace Emby.Server.Implementations.Session var series = episode.Series; if (series != null) { - var episodes = series.GetEpisodes(user, new DtoOptions(false) - { - EnableImages = false - }) + var episodes = series.GetEpisodes( + user, + new DtoOptions(false) + { + EnableImages = false + }) .Where(i => !i.IsVirtualItem) .SkipWhile(i => i.Id != episode.Id) .ToList(); @@ -1100,9 +1140,7 @@ namespace Emby.Server.Implementations.Session return new List(); } - var byName = item as IItemByName; - - if (byName != null) + if (item is IItemByName byName) { return byName.GetTaggedItems(new InternalItemsQuery(user) { @@ -1152,7 +1190,7 @@ namespace Emby.Server.Implementations.Session if (item == null) { - _logger.LogError("A non-existant item Id {0} was passed into TranslateItemForInstantMix", id); + _logger.LogError("A non-existent item Id {0} was passed into TranslateItemForInstantMix", id); return new List(); } @@ -1163,13 +1201,15 @@ namespace Emby.Server.Implementations.Session { var generalCommand = new GeneralCommand { - Name = GeneralCommandType.DisplayContent.ToString() + Name = GeneralCommandType.DisplayContent.ToString(), + Arguments = + { + ["ItemId"] = command.ItemId, + ["ItemName"] = command.ItemName, + ["ItemType"] = command.ItemType + } }; - generalCommand.Arguments["ItemId"] = command.ItemId; - generalCommand.Arguments["ItemName"] = command.ItemName; - generalCommand.Arguments["ItemType"] = command.ItemType; - return SendGeneralCommand(controllingSessionId, sessionId, generalCommand, cancellationToken); } @@ -1410,7 +1450,8 @@ namespace Emby.Server.Implementations.Session var token = GetAuthorizationToken(user, request.DeviceId, request.App, request.AppVersion, request.DeviceName); - var session = LogSessionActivity(request.App, + var session = LogSessionActivity( + request.App, request.AppVersion, request.DeviceId, request.DeviceName, @@ -1454,9 +1495,9 @@ namespace Emby.Server.Implementations.Session { Logout(auth); } - catch + catch (Exception ex) { - + _logger.LogError(ex, "Error while logging out."); } } } @@ -1572,7 +1613,8 @@ namespace Emby.Server.Implementations.Session ReportCapabilities(session, capabilities, true); } - private void ReportCapabilities(SessionInfo session, + private void ReportCapabilities( + SessionInfo session, ClientCapabilities capabilities, bool saveCapabilities) { @@ -1580,10 +1622,12 @@ namespace Emby.Server.Implementations.Session if (saveCapabilities) { - CapabilitiesChanged?.Invoke(this, new SessionEventArgs - { - SessionInfo = session - }); + CapabilitiesChanged?.Invoke( + this, + new SessionEventArgs + { + SessionInfo = session + }); try { diff --git a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs index 535f123f90..0804b01fca 100644 --- a/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs +++ b/Emby.Server.Implementations/Sorting/AlbumArtistComparer.cs @@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Sorting { var audio = x as IHasAlbumArtist; - return audio != null ? audio.AlbumArtists.FirstOrDefault() : null; + return audio?.AlbumArtists.FirstOrDefault(); } /// diff --git a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs index 46e0dd9186..504b6d2838 100644 --- a/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs +++ b/Emby.Server.Implementations/Sorting/SeriesSortNameComparer.cs @@ -18,17 +18,17 @@ namespace Emby.Server.Implementations.Sorting return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); } - private static string GetValue(BaseItem item) - { - var hasSeries = item as IHasSeries; - - return hasSeries != null ? hasSeries.FindSeriesSortName() : null; - } - /// /// Gets the name. /// /// The name. public string Name => ItemSortBy.SeriesSortName; + + private static string GetValue(BaseItem item) + { + var hasSeries = item as IHasSeries; + + return hasSeries?.FindSeriesSortName(); + } } } diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 69673a49c1..49f8c6ace0 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -165,6 +165,7 @@ namespace MediaBrowser.Api { options.ImageTypeLimit = hasDtoOptions.ImageTypeLimit.Value; } + if (hasDtoOptions.EnableUserData.HasValue) { options.EnableUserData = hasDtoOptions.EnableUserData.Value; @@ -307,7 +308,7 @@ namespace MediaBrowser.Api return pathInfo[index]; } - private string[] Parse(string pathUri) + private static string[] Parse(string pathUri) { var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None); @@ -329,38 +330,32 @@ namespace MediaBrowser.Api /// protected BaseItem GetItemByName(string name, string type, ILibraryManager libraryManager, DtoOptions dtoOptions) { - BaseItem item; - - if (type.IndexOf("Person", StringComparison.OrdinalIgnoreCase) == 0) + if (type.Equals("Person", StringComparison.OrdinalIgnoreCase)) { - item = GetPerson(name, libraryManager, dtoOptions); + return GetPerson(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Artist", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Artist", StringComparison.OrdinalIgnoreCase)) { - item = GetArtist(name, libraryManager, dtoOptions); + return GetArtist(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Genre", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Genre", StringComparison.OrdinalIgnoreCase)) { - item = GetGenre(name, libraryManager, dtoOptions); + return GetGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("MusicGenre", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("MusicGenre", StringComparison.OrdinalIgnoreCase)) { - item = GetMusicGenre(name, libraryManager, dtoOptions); + return GetMusicGenre(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Studio", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Studio", StringComparison.OrdinalIgnoreCase)) { - item = GetStudio(name, libraryManager, dtoOptions); + return GetStudio(name, libraryManager, dtoOptions); } - else if (type.IndexOf("Year", StringComparison.OrdinalIgnoreCase) == 0) + else if (type.Equals("Year", StringComparison.OrdinalIgnoreCase)) { - item = libraryManager.GetYear(int.Parse(name)); - } - else - { - throw new ArgumentException(); + return libraryManager.GetYear(int.Parse(name)); } - return item; + throw new ArgumentException("Invalid type", nameof(type)); } } } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 7a8455ff26..a30f8adfed 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -87,11 +87,6 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetArtists request) { - if (string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - //request.IncludeItemTypes = "Audio,MusicVideo"; - } - return GetResultSlim(request); } @@ -102,11 +97,6 @@ namespace MediaBrowser.Api.UserLibrary /// System.Object. public object Get(GetAlbumArtists request) { - if (string.IsNullOrWhiteSpace(request.IncludeItemTypes)) - { - //request.IncludeItemTypes = "Audio,MusicVideo"; - } - var result = GetResultSlim(request); return ToOptimizedResult(result); diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs index f26087fda3..860ea13cf6 100644 --- a/MediaBrowser.Providers/Manager/ProviderManager.cs +++ b/MediaBrowser.Providers/Manager/ProviderManager.cs @@ -942,10 +942,7 @@ namespace MediaBrowser.Providers.Manager _activeRefreshes[id] = 0; } - if (RefreshStarted != null) - { - RefreshStarted(this, new GenericEventArgs(item)); - } + RefreshStarted?.Invoke(this, new GenericEventArgs(item)); } public void OnRefreshComplete(BaseItem item) @@ -956,10 +953,7 @@ namespace MediaBrowser.Providers.Manager _activeRefreshes.Remove(item.Id); } - if (RefreshCompleted != null) - { - RefreshCompleted(this, new GenericEventArgs(item)); - } + RefreshCompleted?.Invoke(this, new GenericEventArgs(item)); } public double? GetRefreshProgress(Guid id) @@ -986,10 +980,7 @@ namespace MediaBrowser.Providers.Manager { _activeRefreshes[id] = progress; - if (RefreshProgress != null) - { - RefreshProgress(this, new GenericEventArgs>(new Tuple(item, progress))); - } + RefreshProgress?.Invoke(this, new GenericEventArgs>(new Tuple(item, progress))); } else { @@ -1079,17 +1070,14 @@ namespace MediaBrowser.Providers.Manager await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); // Collection folders don't validate their children so we'll have to simulate that here - var collectionFolder = item as CollectionFolder; - if (collectionFolder != null) + if (item is CollectionFolder collectionFolder) { await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false); } else { - var folder = item as Folder; - - if (folder != null) + if (item is Folder folder) { await folder.ValidateChildren(new SimpleProgress(), cancellationToken, options).ConfigureAwait(false); } @@ -1098,16 +1086,11 @@ namespace MediaBrowser.Providers.Manager private async Task RefreshCollectionFolderChildren(MetadataRefreshOptions options, CollectionFolder collectionFolder, CancellationToken cancellationToken) { - foreach (var child in collectionFolder.GetPhysicalFolders().ToList()) + foreach (var child in collectionFolder.GetPhysicalFolders()) { await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); - if (child.IsFolder) - { - var folder = (Folder)child; - - await folder.ValidateChildren(new SimpleProgress(), cancellationToken, options, true).ConfigureAwait(false); - } + await child.ValidateChildren(new SimpleProgress(), cancellationToken, options, true).ConfigureAwait(false); } } @@ -1116,20 +1099,18 @@ namespace MediaBrowser.Providers.Manager var albums = _libraryManagerFactory() .GetItemList(new InternalItemsQuery { - IncludeItemTypes = new[] { typeof(MusicAlbum).Name }, + IncludeItemTypes = new[] { nameof(MusicAlbum) }, ArtistIds = new[] { item.Id }, DtoOptions = new DtoOptions(false) { EnableImages = false } }) - .OfType() - .ToList(); + .OfType(); var musicArtists = albums .Select(i => i.MusicArtist) - .Where(i => i != null) - .ToList(); + .Where(i => i != null); var musicArtistRefreshTasks = musicArtists.Select(i => i.ValidateChildren(new SimpleProgress(), cancellationToken, options, true)); diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index aee9921034..f86cdeab9f 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -53,7 +53,7 @@ namespace MediaBrowser.Providers.Music var releaseId = searchInfo.GetReleaseId(); var releaseGroupId = searchInfo.GetReleaseGroupId(); - string url = null; + string url; var isNameSearch = false; bool forceMusicBrainzProper = false; @@ -100,10 +100,10 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private List GetResultsFromResponse(Stream stream) + private IEnumerable GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -149,7 +149,7 @@ namespace MediaBrowser.Providers.Music return result; - }).ToList(); + }); } } } @@ -301,7 +301,7 @@ namespace MediaBrowser.Providers.Music public List> Artists = new List>(); - public static List Parse(XmlReader reader) + public static IEnumerable Parse(XmlReader reader) { reader.MoveToContent(); reader.Read(); @@ -338,13 +338,11 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private static List ParseReleaseList(XmlReader reader) + private static IEnumerable ParseReleaseList(XmlReader reader) { - var list = new List(); - reader.MoveToContent(); reader.Read(); @@ -369,7 +367,7 @@ namespace MediaBrowser.Providers.Music var release = ParseRelease(subReader, releaseId); if (release != null) { - list.Add(release); + yield return release; } } break; @@ -386,8 +384,6 @@ namespace MediaBrowser.Providers.Music reader.Read(); } } - - return list; } private static ReleaseResult ParseRelease(XmlReader reader, string releaseId) @@ -552,7 +548,7 @@ namespace MediaBrowser.Providers.Music return (null, null); } - private static ValueTuple ParseArtistArtistCredit(XmlReader reader, string artistId) + private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId) { reader.MoveToContent(); reader.Read(); @@ -586,7 +582,7 @@ namespace MediaBrowser.Providers.Music } } - return new ValueTuple(name, artistId); + return (name, artistId); } private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken) diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 924b712507..55aab7778a 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Providers.Music { using (var stream = response.Content) { - var results = GetResultsFromResponse(stream); + var results = GetResultsFromResponse(stream).ToList(); if (results.Count > 0) { @@ -74,10 +74,10 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } - private List GetResultsFromResponse(Stream stream) + private IEnumerable GetResultsFromResponse(Stream stream) { using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -126,15 +126,13 @@ namespace MediaBrowser.Providers.Music } } - return new List(); + return Enumerable.Empty(); } } } - private List ParseArtistList(XmlReader reader) + private IEnumerable ParseArtistList(XmlReader reader) { - var list = new List(); - reader.MoveToContent(); reader.Read(); @@ -159,7 +157,7 @@ namespace MediaBrowser.Providers.Music var artist = ParseArtist(subReader, mbzId); if (artist != null) { - list.Add(artist); + yield return artist; } } break; @@ -176,8 +174,6 @@ namespace MediaBrowser.Providers.Music reader.Read(); } } - - return list; } private RemoteSearchResult ParseArtist(XmlReader reader, string artistId) @@ -277,7 +273,7 @@ namespace MediaBrowser.Providers.Music /// /// The name. /// System.String. - private string UrlEncode(string name) + private static string UrlEncode(string name) { return WebUtility.UrlEncode(name); } From 6d3e6d800fd8c6d5cb22271e2177946ab286855f Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Wed, 13 Mar 2019 20:31:21 +0000 Subject: [PATCH 157/166] Only delay making request if necessary When requesting data from MusicBrainz, only delay the request if previous request was less than rate limit ago, instead of always delaying for one second at start. --- .../Music/MusicBrainzAlbumProvider.cs | 62 +++++++------------ .../Music/MusicBrainzArtistProvider.cs | 6 +- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index f86cdeab9f..8a3860d74e 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -28,6 +29,7 @@ namespace MediaBrowser.Providers.Music private readonly IApplicationHost _appHost; private readonly ILogger _logger; private readonly IJsonSerializer _json; + private Stopwatch _stopWatchMusicBrainz = new Stopwatch(); public readonly string MusicBrainzBaseUrl; @@ -45,6 +47,9 @@ namespace MediaBrowser.Providers.Music MusicBrainzBaseUrl = configuration["MusicBrainz:BaseUrl"]; + // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit + _stopWatchMusicBrainz.Start(); + Current = this; } @@ -54,8 +59,6 @@ namespace MediaBrowser.Providers.Music var releaseGroupId = searchInfo.GetReleaseGroupId(); string url; - var isNameSearch = false; - bool forceMusicBrainzProper = false; if (!string.IsNullOrEmpty(releaseId)) { @@ -64,7 +67,6 @@ namespace MediaBrowser.Providers.Music else if (!string.IsNullOrEmpty(releaseGroupId)) { url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); - forceMusicBrainzProper = true; } else { @@ -78,8 +80,6 @@ namespace MediaBrowser.Providers.Music } else { - isNameSearch = true; - // I'm sure there is a better way but for now it resolves search for 12" Mixes var queryName = searchInfo.Name.Replace("\"", string.Empty); @@ -91,7 +91,7 @@ namespace MediaBrowser.Providers.Music if (!string.IsNullOrWhiteSpace(url)) { - using (var response = await GetMusicBrainzResponse(url, isNameSearch, forceMusicBrainzProper, cancellationToken).ConfigureAwait(false)) + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) { using (var stream = response.Content) { @@ -247,7 +247,7 @@ namespace MediaBrowser.Providers.Music WebUtility.UrlEncode(albumName), artistId); - using (var response = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) using (var stream = response.Content) using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -272,7 +272,7 @@ namespace MediaBrowser.Providers.Music WebUtility.UrlEncode(albumName), WebUtility.UrlEncode(artistName)); - using (var response = await GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) using (var stream = response.Content) using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -589,7 +589,7 @@ namespace MediaBrowser.Providers.Music { var url = string.Format("/ws/2/release?release-group={0}", releaseGroupId); - using (var response = await GetMusicBrainzResponse(url, true, true, cancellationToken).ConfigureAwait(false)) + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) using (var stream = response.Content) using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -625,7 +625,7 @@ namespace MediaBrowser.Providers.Music { var url = string.Format("/ws/2/release-group/?query=reid:{0}", releaseEntryId); - using (var response = await GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false)) + using (var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) using (var stream = response.Content) using (var oReader = new StreamReader(stream, Encoding.UTF8)) { @@ -710,34 +710,32 @@ namespace MediaBrowser.Providers.Music return null; } - internal Task GetMusicBrainzResponse(string url, bool isSearch, CancellationToken cancellationToken) - { - return GetMusicBrainzResponse(url, isSearch, false, cancellationToken); - } - /// - /// Gets the music brainz response. + /// Makes request to MusicBrainz server and awaits a response. /// - internal async Task GetMusicBrainzResponse(string url, bool isSearch, bool forceMusicBrainzProper, CancellationToken cancellationToken) + internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - var urlInfo = new MbzUrl(MusicBrainzBaseUrl, 1000); - var throttleMs = urlInfo.throttleMs; + // The Jellyfin user-agent is unrestricted but source IP must not exceed + // one request per second, therefore we rate limit to avoid throttling + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting + const long QueryIntervalMs = 1000u; - if (throttleMs > 0) + // Only delay if necessary + if (_stopWatchMusicBrainz.ElapsedMilliseconds < QueryIntervalMs) { - // MusicBrainz is extremely adamant about limiting to one request per second - _logger.LogDebug("Throttling MusicBrainz by {0}ms", throttleMs.ToString(CultureInfo.InvariantCulture)); - await Task.Delay(throttleMs, cancellationToken).ConfigureAwait(false); + var delayMs = QueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); } - url = urlInfo.url.TrimEnd('/') + url; + _logger.LogDebug("MusicBrainz time since previous request: {0}ms", _stopWatchMusicBrainz.ElapsedMilliseconds); + _stopWatchMusicBrainz.Restart(); var options = new HttpRequestOptions { - Url = url, + Url = MusicBrainzBaseUrl.TrimEnd('/') + url, CancellationToken = cancellationToken, UserAgent = _appHost.ApplicationUserAgent, - BufferContent = throttleMs > 0 + BufferContent = false }; return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); @@ -749,17 +747,5 @@ namespace MediaBrowser.Providers.Music { throw new NotImplementedException(); } - - internal class MbzUrl - { - internal MbzUrl(string url, int throttleMs) - { - this.url = url; - this.throttleMs = throttleMs; - } - - public string url { get; set; } - public int throttleMs { get; set; } - } } } diff --git a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs index 55aab7778a..59280df897 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzArtistProvider.cs @@ -31,7 +31,7 @@ namespace MediaBrowser.Providers.Music { var url = string.Format("/ws/2/artist/?query=arid:{0}", musicBrainzId); - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, false, cancellationToken).ConfigureAwait(false)) + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) { using (var stream = response.Content) { @@ -46,7 +46,7 @@ namespace MediaBrowser.Providers.Music var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch)); - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) { using (var stream = response.Content) { @@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.Music // Try again using the search with accent characters url url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch)); - using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, true, cancellationToken).ConfigureAwait(false)) + using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false)) { using (var stream = response.Content) { From f8bb7a7ff4d87d19a43f97fed557aa92a657a50f Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 14 Mar 2019 00:43:41 +0000 Subject: [PATCH 158/166] Increased interval to 1050ms and moved to class scope Review comments from JustAMan. --- .../Music/MusicBrainzAlbumProvider.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index 8a3860d74e..a1366e0fde 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -33,6 +33,12 @@ namespace MediaBrowser.Providers.Music public readonly string MusicBrainzBaseUrl; + // The Jellyfin user-agent is unrestricted but source IP must not exceed + // one request per second, therefore we rate limit to avoid throttling. + // Be prudent, use a value slightly above the minimun required. + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting + private const long MusicBrainzQueryIntervalMs = 1050u; + public MusicBrainzAlbumProvider( IHttpClient httpClient, IApplicationHost appHost, @@ -715,15 +721,10 @@ namespace MediaBrowser.Providers.Music /// internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - // The Jellyfin user-agent is unrestricted but source IP must not exceed - // one request per second, therefore we rate limit to avoid throttling - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting - const long QueryIntervalMs = 1000u; - - // Only delay if necessary - if (_stopWatchMusicBrainz.ElapsedMilliseconds < QueryIntervalMs) + if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs) { - var delayMs = QueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + // MusicBrainz is extremely adamant about limiting to one request per second + var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); } From ee7bf86e0f76cdd655f9f9766a4b99f5210ce985 Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 14 Mar 2019 22:05:33 +0100 Subject: [PATCH 159/166] Adjusted the Product Name so the User Agent is correct/better. --- BDInfo/Properties/AssemblyInfo.cs | 2 +- DvdLib/Properties/AssemblyInfo.cs | 2 +- Emby.Dlna/Properties/AssemblyInfo.cs | 2 +- Emby.Drawing/Properties/AssemblyInfo.cs | 2 +- Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs | 2 +- Emby.Naming/Properties/AssemblyInfo.cs | 2 +- Emby.Notifications/Properties/AssemblyInfo.cs | 2 +- Emby.Photos/Properties/AssemblyInfo.cs | 2 +- Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs | 2 +- Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs | 2 +- Jellyfin.Server/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Api/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Common/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Controller/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Model/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Providers/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Tests/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs | 2 +- Mono.Nat/Properties/AssemblyInfo.cs | 2 +- RSSDP/Properties/AssemblyInfo.cs | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/BDInfo/Properties/AssemblyInfo.cs b/BDInfo/Properties/AssemblyInfo.cs index 788cf73666..482cd376a1 100644 --- a/BDInfo/Properties/AssemblyInfo.cs +++ b/BDInfo/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs index 5fc055d1f0..09d1a3801a 100644 --- a/DvdLib/Properties/AssemblyInfo.cs +++ b/DvdLib/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.Dlna/Properties/AssemblyInfo.cs b/Emby.Dlna/Properties/AssemblyInfo.cs index 9d3a22c970..0e3b03a322 100644 --- a/Emby.Dlna/Properties/AssemblyInfo.cs +++ b/Emby.Dlna/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Resources; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.Drawing/Properties/AssemblyInfo.cs b/Emby.Drawing/Properties/AssemblyInfo.cs index 8dfefe0af8..4429839606 100644 --- a/Emby.Drawing/Properties/AssemblyInfo.cs +++ b/Emby.Drawing/Properties/AssemblyInfo.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs b/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs index d60eccc2ee..702aed3b5a 100644 --- a/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs +++ b/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.Naming/Properties/AssemblyInfo.cs b/Emby.Naming/Properties/AssemblyInfo.cs index 15311570b8..9295a1ec75 100644 --- a/Emby.Naming/Properties/AssemblyInfo.cs +++ b/Emby.Naming/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.Notifications/Properties/AssemblyInfo.cs b/Emby.Notifications/Properties/AssemblyInfo.cs index fd70375515..53b80e3dc1 100644 --- a/Emby.Notifications/Properties/AssemblyInfo.cs +++ b/Emby.Notifications/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.Photos/Properties/AssemblyInfo.cs b/Emby.Photos/Properties/AssemblyInfo.cs index 262125d388..c3ebc4e08e 100644 --- a/Emby.Photos/Properties/AssemblyInfo.cs +++ b/Emby.Photos/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs b/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs index ff2efb0781..69a532c357 100644 --- a/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs +++ b/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs b/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs index ea1c457f6c..9fdb9529f5 100644 --- a/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs +++ b/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Jellyfin.Server/Properties/AssemblyInfo.cs b/Jellyfin.Server/Properties/AssemblyInfo.cs index 2959cdf1fe..1add32c8c1 100644 --- a/Jellyfin.Server/Properties/AssemblyInfo.cs +++ b/Jellyfin.Server/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.Api/Properties/AssemblyInfo.cs b/MediaBrowser.Api/Properties/AssemblyInfo.cs index f867230311..fb7d43f456 100644 --- a/MediaBrowser.Api/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Api/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.Common/Properties/AssemblyInfo.cs b/MediaBrowser.Common/Properties/AssemblyInfo.cs index 1a8fdb618d..d428cea2fd 100644 --- a/MediaBrowser.Common/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Common/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.Controller/Properties/AssemblyInfo.cs b/MediaBrowser.Controller/Properties/AssemblyInfo.cs index 007a1d739b..0841e84f24 100644 --- a/MediaBrowser.Controller/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Controller/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs index 3eac837081..4d09084d20 100644 --- a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs +++ b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs index 6ecdf89bc1..8851ec5277 100644 --- a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs +++ b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.Model/Properties/AssemblyInfo.cs b/MediaBrowser.Model/Properties/AssemblyInfo.cs index e78719e35f..f118294817 100644 --- a/MediaBrowser.Model/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Model/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.Providers/Properties/AssemblyInfo.cs b/MediaBrowser.Providers/Properties/AssemblyInfo.cs index d2b13fc89d..eb2599e35a 100644 --- a/MediaBrowser.Providers/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Providers/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.Tests/Properties/AssemblyInfo.cs b/MediaBrowser.Tests/Properties/AssemblyInfo.cs index 9bc2a19f21..1bd3ef5d64 100644 --- a/MediaBrowser.Tests/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Tests/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin System")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs b/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs index 416ce4826a..346853f588 100644 --- a/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs +++ b/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs index 11e513f1be..343befb7e2 100644 --- a/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs +++ b/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs index ea1c9dee6d..c24a4fc6c2 100644 --- a/Mono.Nat/Properties/AssemblyInfo.cs +++ b/Mono.Nat/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases unde the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/RSSDP/Properties/AssemblyInfo.cs b/RSSDP/Properties/AssemblyInfo.cs index d2bb7c6f31..690ddb807c 100644 --- a/RSSDP/Properties/AssemblyInfo.cs +++ b/RSSDP/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] -[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")] +[assembly: AssemblyProduct("Jellyfin Server")] [assembly: AssemblyCopyright("Copyright © 2015 Troy Willmot. Code released under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] From 21cc38fcf426f5ecaedbe8a94007a89663de749c Mon Sep 17 00:00:00 2001 From: Erwin de Haan Date: Thu, 14 Mar 2019 22:17:56 +0100 Subject: [PATCH 160/166] Adjusted AssemblyCopyright attribute values. --- BDInfo/Properties/AssemblyInfo.cs | 2 +- DvdLib/Properties/AssemblyInfo.cs | 2 +- Emby.Dlna/Properties/AssemblyInfo.cs | 2 +- Emby.Drawing/Properties/AssemblyInfo.cs | 2 +- Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs | 2 +- Emby.Naming/Properties/AssemblyInfo.cs | 2 +- Emby.Notifications/Properties/AssemblyInfo.cs | 2 +- Emby.Photos/Properties/AssemblyInfo.cs | 2 +- Emby.Server.Implementations/Properties/AssemblyInfo.cs | 2 +- Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs | 2 +- Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs | 2 +- Jellyfin.Server/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Api/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Common/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Controller/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Model/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.Providers/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs | 2 +- MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs | 2 +- Mono.Nat/Properties/AssemblyInfo.cs | 2 +- RSSDP/Properties/AssemblyInfo.cs | 2 +- 23 files changed, 23 insertions(+), 23 deletions(-) diff --git a/BDInfo/Properties/AssemblyInfo.cs b/BDInfo/Properties/AssemblyInfo.cs index 482cd376a1..f65c7036a4 100644 --- a/BDInfo/Properties/AssemblyInfo.cs +++ b/BDInfo/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs index 09d1a3801a..6acd571d68 100644 --- a/DvdLib/Properties/AssemblyInfo.cs +++ b/DvdLib/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Dlna/Properties/AssemblyInfo.cs b/Emby.Dlna/Properties/AssemblyInfo.cs index 0e3b03a322..a2c1e0db8b 100644 --- a/Emby.Dlna/Properties/AssemblyInfo.cs +++ b/Emby.Dlna/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Resources; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Drawing/Properties/AssemblyInfo.cs b/Emby.Drawing/Properties/AssemblyInfo.cs index 4429839606..281008e370 100644 --- a/Emby.Drawing/Properties/AssemblyInfo.cs +++ b/Emby.Drawing/Properties/AssemblyInfo.cs @@ -9,7 +9,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs b/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs index 702aed3b5a..5956fc3b31 100644 --- a/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs +++ b/Emby.IsoMounting/IsoMounter/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Naming/Properties/AssemblyInfo.cs b/Emby.Naming/Properties/AssemblyInfo.cs index 9295a1ec75..f26e0ba794 100644 --- a/Emby.Naming/Properties/AssemblyInfo.cs +++ b/Emby.Naming/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Notifications/Properties/AssemblyInfo.cs b/Emby.Notifications/Properties/AssemblyInfo.cs index 53b80e3dc1..5c82c90c47 100644 --- a/Emby.Notifications/Properties/AssemblyInfo.cs +++ b/Emby.Notifications/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Photos/Properties/AssemblyInfo.cs b/Emby.Photos/Properties/AssemblyInfo.cs index c3ebc4e08e..a3bb4362a9 100644 --- a/Emby.Photos/Properties/AssemblyInfo.cs +++ b/Emby.Photos/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.Server.Implementations/Properties/AssemblyInfo.cs b/Emby.Server.Implementations/Properties/AssemblyInfo.cs index 79ba9374ce..a1933f66ef 100644 --- a/Emby.Server.Implementations/Properties/AssemblyInfo.cs +++ b/Emby.Server.Implementations/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs b/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs index 69a532c357..7beec09cbd 100644 --- a/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs +++ b/Emby.XmlTv/Emby.XmlTv/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs b/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs index 9fdb9529f5..e7db09449d 100644 --- a/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs +++ b/Jellyfin.Drawing.Skia/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Jellyfin.Server/Properties/AssemblyInfo.cs b/Jellyfin.Server/Properties/AssemblyInfo.cs index 1add32c8c1..5de1e653d9 100644 --- a/Jellyfin.Server/Properties/AssemblyInfo.cs +++ b/Jellyfin.Server/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Api/Properties/AssemblyInfo.cs b/MediaBrowser.Api/Properties/AssemblyInfo.cs index fb7d43f456..35bcbea5cd 100644 --- a/MediaBrowser.Api/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Api/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Common/Properties/AssemblyInfo.cs b/MediaBrowser.Common/Properties/AssemblyInfo.cs index d428cea2fd..538e89fd1c 100644 --- a/MediaBrowser.Common/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Common/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Controller/Properties/AssemblyInfo.cs b/MediaBrowser.Controller/Properties/AssemblyInfo.cs index 0841e84f24..60e7923091 100644 --- a/MediaBrowser.Controller/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Controller/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs index 4d09084d20..580cef9da6 100644 --- a/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs +++ b/MediaBrowser.LocalMetadata/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs index 8851ec5277..a9491374bd 100644 --- a/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs +++ b/MediaBrowser.MediaEncoding/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Model/Properties/AssemblyInfo.cs b/MediaBrowser.Model/Properties/AssemblyInfo.cs index f118294817..f99e9ece96 100644 --- a/MediaBrowser.Model/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Model/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.Providers/Properties/AssemblyInfo.cs b/MediaBrowser.Providers/Properties/AssemblyInfo.cs index eb2599e35a..f1c46899ce 100644 --- a/MediaBrowser.Providers/Properties/AssemblyInfo.cs +++ b/MediaBrowser.Providers/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs b/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs index 346853f588..584d490216 100644 --- a/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs +++ b/MediaBrowser.WebDashboard/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs b/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs index 343befb7e2..b3e2f27179 100644 --- a/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs +++ b/MediaBrowser.XbmcMetadata/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs index c24a4fc6c2..dc47f2ffeb 100644 --- a/Mono.Nat/Properties/AssemblyInfo.cs +++ b/Mono.Nat/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases unde the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2006 Alan McGovern. Copyright © 2007 Ben Motmans. Code releases under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] diff --git a/RSSDP/Properties/AssemblyInfo.cs b/RSSDP/Properties/AssemblyInfo.cs index 690ddb807c..55f7b6a834 100644 --- a/RSSDP/Properties/AssemblyInfo.cs +++ b/RSSDP/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("Jellyfin Project")] [assembly: AssemblyProduct("Jellyfin Server")] -[assembly: AssemblyCopyright("Copyright © 2015 Troy Willmot. Code released under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")] +[assembly: AssemblyCopyright("Copyright © 2015 Troy Willmot. Code released under the MIT license. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: NeutralResourcesLanguage("en")] From d125fbc43d453be7de08375dede1748d4c19a8cf Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Thu, 14 Mar 2019 21:32:27 +0000 Subject: [PATCH 161/166] Added contact email to user agent MusicBrainz request a contact email address is supplied in comment section of user agent field. --- Emby.Server.Implementations/ApplicationHost.cs | 6 ++++++ MediaBrowser.Common/IApplicationHost.cs | 6 ++++++ MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs | 5 +++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 94eaffa479..eaea8844d4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -428,6 +428,12 @@ namespace Emby.Server.Implementations /// The application user agent. public string ApplicationUserAgent => Name.Replace(' ','-') + "/" + ApplicationVersion; + /// + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// + public string ApplicationUserAgentAddress { get; } = "team@jellyfin.org"; + private string _productName; /// diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index 966448d630..2925a3efd9 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -71,6 +71,12 @@ namespace MediaBrowser.Common /// The application user agent. string ApplicationUserAgent { get; } + /// + /// Gets the email address for use within a comment section of a user agent field. + /// Presently used to provide contact information to MusicBrainz service. + /// + string ApplicationUserAgentAddress { get; } + /// /// Gets the exports. /// diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index a1366e0fde..b716f40f04 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -735,7 +734,9 @@ namespace MediaBrowser.Providers.Music { Url = MusicBrainzBaseUrl.TrimEnd('/') + url, CancellationToken = cancellationToken, - UserAgent = _appHost.ApplicationUserAgent, + // MusicBrainz request a contact email address is supplied, as comment, in user agent field: + // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting#User-Agent + UserAgent = string.Format("{0} ( {1} )", _appHost.ApplicationUserAgent, _appHost.ApplicationUserAgentAddress), BufferContent = false }; From dd929d796f10d06f5874909d7572fa49e750fb6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 15 Mar 2019 14:30:15 +0100 Subject: [PATCH 162/166] Only remove /var/lib/apt/lists/* --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 72c0cdbf3a..5794bdde1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN apt-get update \ libfontconfig1 \ && apt-get clean autoclean \ && apt-get autoremove \ - && rm -rf /var/lib/{apt,dpkg,cache,log} \ + && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media COPY --from=ffmpeg / / From 31305af7ffafabaf88911b67665751f74903199e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Fri, 15 Mar 2019 14:37:11 +0100 Subject: [PATCH 163/166] Clean apt lists in arm Dockerfiles --- Dockerfile.arm | 1 + Dockerfile.arm64 | 1 + 2 files changed, 2 insertions(+) diff --git a/Dockerfile.arm b/Dockerfile.arm index 238e90ccd8..1497da0ef7 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -25,6 +25,7 @@ FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7 COPY --from=qemu_extract qemu-arm-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ + && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 2927d4e658..f4658a055c 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -26,6 +26,7 @@ FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8 COPY --from=qemu_extract qemu-aarch64-static /usr/bin RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \ + && rm -rf /var/lib/apt/lists/* \ && mkdir -p /cache /config /media \ && chmod 777 /cache /config /media COPY --from=builder /jellyfin /jellyfin From 764c901cd7adc5ae95d2657822d2910e0c2e8468 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Fri, 15 Mar 2019 17:34:15 +0100 Subject: [PATCH 164/166] Fix exception caused by #1096 ```cs MediaBrowser.Common.Extensions.ResourceNotFoundException: Configuration with key subtitles not found. at Emby.Server.Implementations.AppBase.BaseConfigurationManager.<>c__DisplayClass42_0.b__0(String k) in /home/pi/dev/jellyfin/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs:line 247 at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory) at Emby.Server.Implementations.AppBase.BaseConfigurationManager.GetConfiguration(String key) in /home/pi/dev/jellyfin/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs:line 238 at MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo.AddExternalSubtitles(Video video, List`1 currentStreams, MetadataRefreshOptions options, CancellationToken cancellationToken) in /home/pi/dev/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs:line 486 at MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo.Fetch(Video video, CancellationToken cancellationToken, MediaInfo mediaInfo, BlurayDiscInfo blurayInfo, MetadataRefreshOptions options) in /home/pi/dev/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs:line 204 at MediaBrowser.Providers.MediaInfo.FFProbeVideoInfo.ProbeVideo[T](T item, MetadataRefreshOptions options, CancellationToken cancellationToken) in /home/pi/dev/jellyfin/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs:line 119 at MediaBrowser.Providers.Manager.MetadataService`2.RunCustomProvider(ICustomMetadataProvider`1 provider, TItemType item, String logName, MetadataRefreshOptions options, RefreshResult refreshResult, CancellationToken cancellationToken) in /home/pi/dev/jellyfin/MediaBrowser.Providers/Manager/MetadataService.cs:line 806 ``` --- .../Providers/SubtitleConfigurationFactory.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs diff --git a/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs new file mode 100644 index 0000000000..09d974db6c --- /dev/null +++ b/MediaBrowser.Common/Providers/SubtitleConfigurationFactory.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Providers; + +namespace MediaBrowser.Common.Providers +{ + public class SubtitleConfigurationFactory : IConfigurationFactory + { + public IEnumerable GetConfigurations() + { + yield return new ConfigurationStore() + { + Key = "subtitles", + ConfigurationType = typeof(SubtitleOptions) + }; + } + } +} From d2e408539ea32eaad573ff1deb6f77a2d80a70d3 Mon Sep 17 00:00:00 2001 From: PloughPuff Date: Fri, 15 Mar 2019 19:29:04 +0000 Subject: [PATCH 165/166] MusicBrainz 503 Retry Strategy Upon receiving back a 503 Service Unavailable from MusicBrainz (indicating throttling), retry the same request a number of times before giving up. --- .../Music/MusicBrainzAlbumProvider.cs | 63 ++++++++++++++----- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs index b716f40f04..3797f9039a 100644 --- a/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs +++ b/MediaBrowser.Providers/Music/MusicBrainzAlbumProvider.cs @@ -32,12 +32,21 @@ namespace MediaBrowser.Providers.Music public readonly string MusicBrainzBaseUrl; - // The Jellyfin user-agent is unrestricted but source IP must not exceed - // one request per second, therefore we rate limit to avoid throttling. - // Be prudent, use a value slightly above the minimun required. - // https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting + /// + /// The Jellyfin user-agent is unrestricted but source IP must not exceed + /// one request per second, therefore we rate limit to avoid throttling. + /// Be prudent, use a value slightly above the minimun required. + /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting + /// private const long MusicBrainzQueryIntervalMs = 1050u; + /// + /// For each single MB lookup/search, this is the maximum number of + /// attempts that shall be made whilst receiving a 503 Server + /// Unavailable (indicating throttled) response. + /// + private const uint MusicBrainzQueryAttempts = 5u; + public MusicBrainzAlbumProvider( IHttpClient httpClient, IApplicationHost appHost, @@ -717,19 +726,12 @@ namespace MediaBrowser.Providers.Music /// /// Makes request to MusicBrainz server and awaits a response. + /// A 503 Service Unavailable response indicates throttling to maintain a rate limit. + /// A number of retries shall be made in order to try and satisfy the request before + /// giving up and returning null. /// internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken) { - if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs) - { - // MusicBrainz is extremely adamant about limiting to one request per second - var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; - await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); - } - - _logger.LogDebug("MusicBrainz time since previous request: {0}ms", _stopWatchMusicBrainz.ElapsedMilliseconds); - _stopWatchMusicBrainz.Restart(); - var options = new HttpRequestOptions { Url = MusicBrainzBaseUrl.TrimEnd('/') + url, @@ -740,7 +742,38 @@ namespace MediaBrowser.Providers.Music BufferContent = false }; - return await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); + HttpResponseInfo response; + var attempts = 0u; + + do + { + attempts++; + + if (_stopWatchMusicBrainz.ElapsedMilliseconds < MusicBrainzQueryIntervalMs) + { + // MusicBrainz is extremely adamant about limiting to one request per second + var delayMs = MusicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds; + await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false); + } + + // Write time since last request to debug log as evidence we're meeting rate limit + // requirement, before resetting stopwatch back to zero. + _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds); + _stopWatchMusicBrainz.Restart(); + + response = await _httpClient.SendAsync(options, "GET").ConfigureAwait(false); + + // We retry a finite number of times, and only whilst MB is indcating 503 (throttling) + } + while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable); + + // Log error if unable to query MB database due to throttling + if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable ) + { + _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, options.Url); + } + + return response; } public int Order => 0; From 221389089cc9ca4b69907d6bf3e9d38bf94393ea Mon Sep 17 00:00:00 2001 From: Phallacy Date: Fri, 15 Mar 2019 21:25:19 -0700 Subject: [PATCH 166/166] quick fix for auth bug --- .../Library/DefaultAuthenticationProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs index 3ec1f81d31..3d15a8afbb 100644 --- a/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs +++ b/Emby.Server.Implementations/Library/DefaultAuthenticationProvider.cs @@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Library PasswordHash readyHash = new PasswordHash(resolvedUser.Password); byte[] calculatedHash; string calculatedHashString; - if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id)) + if (_cryptographyProvider.GetSupportedHashMethods().Contains(readyHash.Id) || _cryptographyProvider.DefaultHashMethod == readyHash.Id) { if (string.IsNullOrEmpty(readyHash.Salt)) {