diff options
Diffstat (limited to 'MediaBrowser.Common')
23 files changed, 428 insertions, 107 deletions
diff --git a/MediaBrowser.Common/Crc32.cs b/MediaBrowser.Common/Crc32.cs new file mode 100644 index 000000000..599eb4c99 --- /dev/null +++ b/MediaBrowser.Common/Crc32.cs @@ -0,0 +1,89 @@ +#pragma warning disable CS1591 + +using System; + +namespace MediaBrowser.Common +{ + public static class Crc32 + { + private static readonly uint[] _crcTable = + { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, + 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, + 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, + 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, + 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, + 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, + 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, + 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, + 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, + 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, + 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, + 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, + 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, + 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, + 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, + 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, + 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, + 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, + 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, + 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, + 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, + 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + public static uint Compute(ReadOnlySpan<byte> bytes) + { + var crc = 0xffffffff; + var len = bytes.Length; + for (var i = 0; i < len; i++) + { + crc = (crc >> 8) ^ _crcTable[(bytes[i] ^ crc) & 0xff]; + } + + return ~crc; + } + } +} diff --git a/MediaBrowser.Common/Cryptography/PasswordHash.cs b/MediaBrowser.Common/Cryptography/PasswordHash.cs index 3e2eae1c8..ec21d0580 100644 --- a/MediaBrowser.Common/Cryptography/PasswordHash.cs +++ b/MediaBrowser.Common/Cryptography/PasswordHash.cs @@ -1,8 +1,8 @@ #pragma warning disable CS1591 +#nullable enable using System; using System.Collections.Generic; -using System.IO; using System.Text; namespace MediaBrowser.Common.Cryptography @@ -30,6 +30,16 @@ namespace MediaBrowser.Common.Cryptography public PasswordHash(string id, byte[] hash, byte[] salt, Dictionary<string, string> parameters) { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (id.Length == 0) + { + throw new ArgumentException("String can't be empty", nameof(id)); + } + Id = id; _hash = hash; _salt = salt; @@ -59,58 +69,109 @@ namespace MediaBrowser.Common.Cryptography /// <value>Return the hashed password.</value> public ReadOnlySpan<byte> Hash => _hash; - public static PasswordHash Parse(string hashString) + public static PasswordHash Parse(ReadOnlySpan<char> hashString) { - // The string should at least contain the hash function and the hash itself - string[] splitted = hashString.Split('$'); - if (splitted.Length < 3) + if (hashString.IsEmpty) + { + throw new ArgumentException("String can't be empty", nameof(hashString)); + } + + if (hashString[0] != '$') { - throw new ArgumentException("String doesn't contain enough segments", nameof(hashString)); + throw new FormatException("Hash string must start with a $"); } - // Start at 1, the first index shouldn't contain any data - int index = 1; + // Ignore first $ + hashString = hashString[1..]; - // Name of the hash function - string id = splitted[index++]; + int nextSegment = hashString.IndexOf('$'); + if (hashString.IsEmpty || nextSegment == 0) + { + throw new FormatException("Hash string must contain a valid id"); + } + else if (nextSegment == -1) + { + return new PasswordHash(hashString.ToString(), Array.Empty<byte>()); + } + + ReadOnlySpan<char> id = hashString[..nextSegment]; + hashString = hashString[(nextSegment + 1)..]; + Dictionary<string, string>? parameters = null; + + nextSegment = hashString.IndexOf('$'); // Optional parameters - Dictionary<string, string> parameters = new Dictionary<string, string>(); - if (splitted[index].IndexOf('=', StringComparison.Ordinal) != -1) + ReadOnlySpan<char> parametersSpan = nextSegment == -1 ? hashString : hashString[..nextSegment]; + if (parametersSpan.Contains('=')) { - foreach (string paramset in splitted[index++].Split(',')) + while (!parametersSpan.IsEmpty) { - if (string.IsNullOrEmpty(paramset)) + ReadOnlySpan<char> parameter; + int index = parametersSpan.IndexOf(','); + if (index == -1) + { + parameter = parametersSpan; + parametersSpan = ReadOnlySpan<char>.Empty; + } + else { - continue; + parameter = parametersSpan[..index]; + parametersSpan = parametersSpan[(index + 1)..]; } - string[] fields = paramset.Split('='); - if (fields.Length != 2) + int splitIndex = parameter.IndexOf('='); + if (splitIndex == -1 || splitIndex == 0 || splitIndex == parameter.Length - 1) { - throw new InvalidDataException($"Malformed parameter in password hash string {paramset}"); + throw new FormatException("Malformed parameter in password hash string"); } - parameters.Add(fields[0], fields[1]); + (parameters ??= new Dictionary<string, string>()).Add( + parameter[..splitIndex].ToString(), + parameter[(splitIndex + 1)..].ToString()); + } + + if (nextSegment == -1) + { + // parameters can't be null here + return new PasswordHash(id.ToString(), Array.Empty<byte>(), Array.Empty<byte>(), parameters!); } + + hashString = hashString[(nextSegment + 1)..]; + nextSegment = hashString.IndexOf('$'); + } + + if (nextSegment == 0) + { + throw new FormatException("Hash string contains an empty segment"); } byte[] hash; byte[] salt; - // Check if the string also contains a salt - if (splitted.Length - index == 2) + if (nextSegment == -1) { - salt = Convert.FromHexString(splitted[index++]); - hash = Convert.FromHexString(splitted[index++]); + salt = Array.Empty<byte>(); + hash = Convert.FromHexString(hashString); } else { - salt = Array.Empty<byte>(); - hash = Convert.FromHexString(splitted[index++]); + salt = Convert.FromHexString(hashString[..nextSegment]); + hashString = hashString[(nextSegment + 1)..]; + nextSegment = hashString.IndexOf('$'); + if (nextSegment != -1) + { + throw new FormatException("Hash string contains too many segments"); + } + + if (hashString.IsEmpty) + { + throw new FormatException("Hash segment is empty"); + } + + hash = Convert.FromHexString(hashString); } - return new PasswordHash(id, hash, salt, parameters); + return new PasswordHash(id.ToString(), hash, salt, parameters ?? new Dictionary<string, string>()); } private void SerializeParameters(StringBuilder stringBuilder) @@ -147,8 +208,13 @@ namespace MediaBrowser.Common.Cryptography .Append(Convert.ToHexString(_salt)); } - return str.Append('$') - .Append(Convert.ToHexString(_hash)).ToString(); + if (_hash.Length != 0) + { + str.Append('$') + .Append(Convert.ToHexString(_hash)); + } + + return str.ToString(); } } } diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs index 19fa95480..e51ad42d1 100644 --- a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs +++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Extensions /// </summary> /// <param name="context">The HTTP context.</param> /// <returns>The remote caller IP address.</returns> - public static string GetNormalizedRemoteIp(this HttpContext context) + public static IPAddress GetNormalizedRemoteIp(this HttpContext context) { // Default to the loopback address if no RemoteIpAddress is specified (i.e. during integration tests) var ip = context.Connection.RemoteIpAddress ?? IPAddress.Loopback; @@ -35,7 +35,7 @@ namespace MediaBrowser.Common.Extensions ip = ip.MapToIPv4(); } - return ip.ToString(); + return ip; } } } diff --git a/MediaBrowser.Common/IApplicationHost.cs b/MediaBrowser.Common/IApplicationHost.cs index ddcf2ac17..c3e4ed6db 100644 --- a/MediaBrowser.Common/IApplicationHost.cs +++ b/MediaBrowser.Common/IApplicationHost.cs @@ -10,7 +10,7 @@ namespace MediaBrowser.Common /// </summary> /// <param name="type">Type to create.</param> /// <returns>New instance of type <param>type</param>.</returns> - public delegate object CreationDelegate(Type type); + public delegate object CreationDelegateFactory(Type type); /// <summary> /// An interface to be implemented by the applications hosting a kernel. @@ -112,7 +112,7 @@ namespace MediaBrowser.Common /// <param name="defaultFunc">Delegate function that gets called to create the object.</param> /// <param name="manageLifetime">If set to <c>true</c> [manage lifetime].</param> /// <returns><see cref="IReadOnlyCollection{T}" />.</returns> - IReadOnlyCollection<T> GetExports<T>(CreationDelegate defaultFunc, bool manageLifetime = true); + IReadOnlyCollection<T> GetExports<T>(CreationDelegateFactory defaultFunc, bool manageLifetime = true); /// <summary> /// Gets the export types. diff --git a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs index 38a7e1d20..2ec702165 100644 --- a/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonCommaDelimitedArrayConverter.cs @@ -26,8 +26,8 @@ namespace MediaBrowser.Common.Json.Converters { if (reader.TokenType == JsonTokenType.String) { - var stringEntries = reader.GetString()?.Split(',', StringSplitOptions.RemoveEmptyEntries); - if (stringEntries == null || stringEntries.Length == 0) + var stringEntries = reader.GetString().Split(',', StringSplitOptions.RemoveEmptyEntries); + if (stringEntries.Length == 0) { return Array.Empty<T>(); } @@ -69,7 +69,7 @@ namespace MediaBrowser.Common.Json.Converters /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + throw new NotImplementedException(); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs index cb3d83f58..3d97a9de5 100644 --- a/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonOmdbNotAvailableInt32Converter.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Json.Converters return (int?)converter.ConvertFromString(str); } - return JsonSerializer.Deserialize<int?>(ref reader, options); + return JsonSerializer.Deserialize<int>(ref reader, options); } /// <inheritdoc /> diff --git a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs index 377db1a44..c408a3be1 100644 --- a/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonPipeDelimitedArrayConverter.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Common.Json.Converters /// <inheritdoc /> public override void Write(Utf8JsonWriter writer, T[] value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, value, options); + throw new NotImplementedException(); } } } diff --git a/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs new file mode 100644 index 000000000..669b3cd07 --- /dev/null +++ b/MediaBrowser.Common/Json/Converters/JsonStringConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Buffers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace MediaBrowser.Common.Json.Converters +{ + /// <summary> + /// Converter to allow the serializer to read strings. + /// </summary> + public class JsonStringConverter : JsonConverter<string> + { + /// <inheritdoc /> + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.TokenType switch + { + JsonTokenType.Null => null, + JsonTokenType.String => reader.GetString(), + _ => GetRawValue(reader) + }; + } + + /// <inheritdoc /> + public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + + private static string GetRawValue(Utf8JsonReader reader) + { + var utf8Bytes = reader.HasValueSequence + ? reader.ValueSequence.ToArray() + : reader.ValueSpan; + return Encoding.UTF8.GetString(utf8Bytes); + } + } +}
\ No newline at end of file diff --git a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs index 37e6f64e3..f69e868cc 100644 --- a/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs +++ b/MediaBrowser.Common/Json/Converters/JsonVersionConverter.cs @@ -7,6 +7,9 @@ namespace MediaBrowser.Common.Json.Converters /// <summary> /// Converts a Version object or value to/from JSON. /// </summary> + /// <remarks> + /// Required to send <see cref="Version"/> as a string instead of an object. + /// </remarks> public class JsonVersionConverter : JsonConverter<Version> { /// <inheritdoc /> diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs index 2ef24a884..405d6125f 100644 --- a/MediaBrowser.Common/Json/JsonDefaults.cs +++ b/MediaBrowser.Common/Json/JsonDefaults.cs @@ -39,7 +39,8 @@ namespace MediaBrowser.Common.Json new JsonStringEnumConverter(), new JsonNullableStructConverterFactory(), new JsonBoolNumberConverter(), - new JsonDateTimeConverter() + new JsonDateTimeConverter(), + new JsonStringConverter() } }; @@ -61,7 +62,7 @@ namespace MediaBrowser.Common.Json /// If the defaults must be modified the author must use the copy constructor. /// </remarks> /// <returns>The default <see cref="JsonSerializerOptions" /> options.</returns> - public static JsonSerializerOptions GetOptions() + public static JsonSerializerOptions Options => _jsonSerializerOptions; /// <summary> @@ -72,7 +73,7 @@ namespace MediaBrowser.Common.Json /// If the defaults must be modified the author must use the copy constructor. /// </remarks> /// <returns>The camelCase <see cref="JsonSerializerOptions" /> options.</returns> - public static JsonSerializerOptions GetCamelCaseOptions() + public static JsonSerializerOptions CamelCaseOptions => _camelCaseJsonSerializerOptions; /// <summary> @@ -83,7 +84,7 @@ namespace MediaBrowser.Common.Json /// If the defaults must be modified the author must use the copy constructor. /// </remarks> /// <returns>The PascalCase <see cref="JsonSerializerOptions" /> options.</returns> - public static JsonSerializerOptions GetPascalCaseOptions() + public static JsonSerializerOptions PascalCaseOptions => _pascalCaseJsonSerializerOptions; } } diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index e469436a9..0d9f78704 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -14,7 +14,7 @@ </PropertyGroup> <ItemGroup> - <FrameworkReference Include="Microsoft.AspNetCore.App"/> + <FrameworkReference Include="Microsoft.AspNetCore.App" /> <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> </ItemGroup> @@ -33,6 +33,8 @@ <GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateDocumentationFile>true</GenerateDocumentationFile> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> + <AnalysisMode>AllEnabledByDefault</AnalysisMode> + <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> <PublishRepositoryUrl>true</PublishRepositoryUrl> <EmbedUntrackedSources>true</EmbedUntrackedSources> <IncludeSymbols>true</IncludeSymbols> @@ -46,16 +48,11 @@ <!-- Code analyzers--> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> - <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" /> <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" /> <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" /> </ItemGroup> - <PropertyGroup Condition=" '$(Configuration)' == 'Debug' "> - <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> - </PropertyGroup> - <ItemGroup> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <_Parameter1>Jellyfin.Common.Tests</_Parameter1> diff --git a/MediaBrowser.Common/Net/INetworkManager.cs b/MediaBrowser.Common/Net/INetworkManager.cs index b6c390d23..185df5b77 100644 --- a/MediaBrowser.Common/Net/INetworkManager.cs +++ b/MediaBrowser.Common/Net/INetworkManager.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Net; using System.Net.NetworkInformation; -using MediaBrowser.Common.Net; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Common.Net @@ -229,5 +228,12 @@ namespace MediaBrowser.Common.Net /// <param name="filter">Optional filter for the list.</param> /// <returns>Returns a filtered list of LAN addresses.</returns> Collection<IPObject> GetFilteredLANSubnets(Collection<IPObject>? filter = null); + + /// <summary> + /// Checks to see if <paramref name="remoteIp"/> has access. + /// </summary> + /// <param name="remoteIp">IP Address of client.</param> + /// <returns><b>True</b> if has access, otherwise <b>false</b>.</returns> + bool HasRemoteAccess(IPAddress remoteIp); } } diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index 4a7c70190..fb3ef9b12 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -135,7 +135,7 @@ namespace MediaBrowser.Common.Net } // See if it's an IPv6 with port address e.g. [::1] or [::1]:120. - int i = host.IndexOf("]", StringComparison.OrdinalIgnoreCase); + int i = host.IndexOf(']', StringComparison.Ordinal); if (i != -1) { return TryParse(host.Remove(i - 1).TrimStart(' ', '['), out hostObj); @@ -389,8 +389,8 @@ namespace MediaBrowser.Common.Net /// <inheritdoc/> protected override IPObject CalculateNetworkAddress() { - var netAddr = NetworkAddressOf(this[0], PrefixLength); - return new IPNetAddress(netAddr.Address, netAddr.PrefixLength); + var (address, prefixLength) = NetworkAddressOf(this[0], PrefixLength); + return new IPNetAddress(address, prefixLength); } /// <summary> @@ -406,7 +406,7 @@ namespace MediaBrowser.Common.Net } // If we haven't resolved before, or our timer has run out... - if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved?.AddMinutes(Timeout))) + if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved.Value.AddMinutes(Timeout))) { _lastResolved = DateTime.UtcNow; ResolveHostInternal().GetAwaiter().GetResult(); @@ -427,7 +427,7 @@ namespace MediaBrowser.Common.Net // Resolves the host name - so save a DNS lookup. if (string.Equals(HostName, "localhost", StringComparison.OrdinalIgnoreCase)) { - _addresses = new IPAddress[] { new IPAddress(Ipv4Loopback), new IPAddress(Ipv6Loopback) }; + _addresses = new IPAddress[] { IPAddress.Loopback, IPAddress.IPv6Loopback }; return; } diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 5fab52eac..589aad4b0 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Common.Net /// <summary> /// IP6Loopback address host. /// </summary> - public static readonly IPNetAddress IP6Loopback = IPNetAddress.Parse("::1"); + public static readonly IPNetAddress IP6Loopback = new IPNetAddress(IPAddress.IPv6Loopback); /// <summary> /// Object's IP address. @@ -113,7 +113,7 @@ namespace MediaBrowser.Common.Net } // Is it a network? - string[] tokens = addr.Split("/"); + string[] tokens = addr.Split('/'); if (tokens.Length == 2) { @@ -171,8 +171,8 @@ namespace MediaBrowser.Common.Net address = address.MapToIPv4(); } - var altAddress = NetworkAddressOf(address, PrefixLength); - return NetworkAddress.Address.Equals(altAddress.Address) && NetworkAddress.PrefixLength >= altAddress.PrefixLength; + var (altAddress, altPrefix) = NetworkAddressOf(address, PrefixLength); + return NetworkAddress.Address.Equals(altAddress) && NetworkAddress.PrefixLength >= altPrefix; } /// <inheritdoc/> @@ -196,8 +196,8 @@ namespace MediaBrowser.Common.Net return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength; } - var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength); - return NetworkAddress.Address.Equals(altAddress.Address); + var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).address; + return NetworkAddress.Address.Equals(altAddress); } return false; @@ -216,11 +216,11 @@ namespace MediaBrowser.Common.Net } /// <inheritdoc/> - public override bool Equals(IPAddress address) + public override bool Equals(IPAddress ip) { - if (address != null && !address.Equals(IPAddress.None) && !Address.Equals(IPAddress.None)) + if (ip != null && !ip.Equals(IPAddress.None) && !Address.Equals(IPAddress.None)) { - return address.Equals(Address); + return ip.Equals(Address); } return false; @@ -270,8 +270,8 @@ namespace MediaBrowser.Common.Net /// <inheritdoc/> protected override IPObject CalculateNetworkAddress() { - var value = NetworkAddressOf(_address, PrefixLength); - return new IPNetAddress(value.Address, value.PrefixLength); + var (address, prefixLength) = NetworkAddressOf(_address, PrefixLength); + return new IPNetAddress(address, prefixLength); } } } diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index 69cd57f8a..3542dcd75 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -11,16 +11,6 @@ namespace MediaBrowser.Common.Net public abstract class IPObject : IEquatable<IPObject> { /// <summary> - /// IPv6 Loopback address. - /// </summary> - protected static readonly byte[] Ipv6Loopback = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; - - /// <summary> - /// IPv4 Loopback address. - /// </summary> - protected static readonly byte[] Ipv4Loopback = { 127, 0, 0, 1 }; - - /// <summary> /// The network address of this object. /// </summary> private IPObject? _networkAddress; @@ -64,7 +54,7 @@ namespace MediaBrowser.Common.Net /// <param name="address">IP Address to convert.</param> /// <param name="prefixLength">Subnet prefix.</param> /// <returns>IPAddress.</returns> - public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) + public static (IPAddress address, byte prefixLength) NetworkAddressOf(IPAddress address, byte prefixLength) { if (address == null) { @@ -78,7 +68,7 @@ namespace MediaBrowser.Common.Net if (IsLoopback(address)) { - return (Address: address, PrefixLength: prefixLength); + return (address, prefixLength); } // An ip address is just a list of bytes, each one representing a segment on the network. @@ -110,7 +100,7 @@ namespace MediaBrowser.Common.Net } // Return the network address for the prefix. - return (Address: new IPAddress(addressBytes), PrefixLength: prefixLength); + return (new IPAddress(addressBytes), prefixLength); } /// <summary> diff --git a/MediaBrowser.Common/Net/NetworkExtensions.cs b/MediaBrowser.Common/Net/NetworkExtensions.cs index 9c1a0cf49..264bfacb4 100644 --- a/MediaBrowser.Common/Net/NetworkExtensions.cs +++ b/MediaBrowser.Common/Net/NetworkExtensions.cs @@ -27,9 +27,11 @@ namespace MediaBrowser.Common.Net /// </summary> /// <param name="source">The <see cref="Collection{IPObject}"/>.</param> /// <param name="item">Item to add.</param> - public static void AddItem(this Collection<IPObject> source, IPObject item) + /// <param name="itemsAreNetworks">If <c>true</c> the values are treated as subnets. + /// If <b>false</b> items are addresses.</param> + public static void AddItem(this Collection<IPObject> source, IPObject item, bool itemsAreNetworks = true) { - if (!source.ContainsAddress(item)) + if (!source.ContainsAddress(item) || !itemsAreNetworks) { source.Add(item); } @@ -190,8 +192,9 @@ namespace MediaBrowser.Common.Net /// </summary> /// <param name="source">The <see cref="Collection{IPObject}"/>.</param> /// <param name="excludeList">Items to exclude.</param> + /// <param name="isNetwork">Collection is a network collection.</param> /// <returns>A new collection, with the items excluded.</returns> - public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList) + public static Collection<IPObject> Exclude(this Collection<IPObject> source, Collection<IPObject> excludeList, bool isNetwork) { if (source.Count == 0 || excludeList == null) { @@ -216,7 +219,7 @@ namespace MediaBrowser.Common.Net if (!found) { - results.AddItem(outer); + results.AddItem(outer, isNetwork); } } @@ -229,7 +232,7 @@ namespace MediaBrowser.Common.Net /// <param name="source">The <see cref="Collection{IPObject}"/>.</param> /// <param name="target">Collection to compare with.</param> /// <returns>A collection containing all the matches.</returns> - public static Collection<IPObject> Union(this Collection<IPObject> source, Collection<IPObject> target) + public static Collection<IPObject> ThatAreContainedInNetworks(this Collection<IPObject> source, Collection<IPObject> target) { if (source.Count == 0) { diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs index 7b162c0e1..ad5a7338d 100644 --- a/MediaBrowser.Common/Plugins/BasePlugin.cs +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -50,7 +50,7 @@ namespace MediaBrowser.Common.Plugins /// Gets a value indicating whether the plugin can be uninstalled. /// </summary> public bool CanUninstall => !Path.GetDirectoryName(AssemblyFilePath) - .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.InvariantCulture); + .Equals(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), StringComparison.Ordinal); /// <summary> /// Gets the plugin info. diff --git a/MediaBrowser.Common/Plugins/BasePluginOfT.cs b/MediaBrowser.Common/Plugins/BasePluginOfT.cs index d5c780851..99c226f50 100644 --- a/MediaBrowser.Common/Plugins/BasePluginOfT.cs +++ b/MediaBrowser.Common/Plugins/BasePluginOfT.cs @@ -39,29 +39,27 @@ namespace MediaBrowser.Common.Plugins { ApplicationPaths = applicationPaths; XmlSerializer = xmlSerializer; - if (this is IPluginAssembly assemblyPlugin) - { - var assembly = GetType().Assembly; - var assemblyName = assembly.GetName(); - var assemblyFilePath = assembly.Location; - var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); - if (!Directory.Exists(dataFolderPath) && Version != null) - { - // Try again with the version number appended to the folder name. - dataFolderPath = dataFolderPath + "_" + Version.ToString(); - } + var assembly = GetType().Assembly; + var assemblyName = assembly.GetName(); + var assemblyFilePath = assembly.Location; - assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); + var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath)); + if (!Directory.Exists(dataFolderPath) && Version != null) + { + // Try again with the version number appended to the folder name. + dataFolderPath = dataFolderPath + "_" + Version.ToString(); + } - var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); - if (idAttributes.Length > 0) - { - var attribute = (GuidAttribute)idAttributes[0]; - var assemblyId = new Guid(attribute.Value); + SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); - assemblyPlugin.SetId(assemblyId); - } + var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true); + if (idAttributes.Length > 0) + { + var attribute = (GuidAttribute)idAttributes[0]; + var assemblyId = new Guid(attribute.Value); + + SetId(assemblyId); } } diff --git a/MediaBrowser.Common/Plugins/IPluginManager.cs b/MediaBrowser.Common/Plugins/IPluginManager.cs index fc2fcb517..0e2e814cb 100644 --- a/MediaBrowser.Common/Plugins/IPluginManager.cs +++ b/MediaBrowser.Common/Plugins/IPluginManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Updates; using Microsoft.Extensions.DependencyInjection; @@ -17,7 +18,7 @@ namespace MediaBrowser.Common.Plugins /// <summary> /// Gets the Plugins. /// </summary> - IList<LocalPlugin> Plugins { get; } + IReadOnlyList<LocalPlugin> Plugins { get; } /// <summary> /// Creates the plugins. @@ -51,8 +52,9 @@ namespace MediaBrowser.Common.Plugins /// <param name="packageInfo">The <see cref="PackageInfo"/> used to generate a manifest.</param> /// <param name="version">Version to be installed.</param> /// <param name="path">The path where to save the manifest.</param> + /// <param name="status">Initial status of the plugin.</param> /// <returns>True if successful.</returns> - Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path); + Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status); /// <summary> /// Imports plugin details from a folder. diff --git a/MediaBrowser.Common/Plugins/LocalPlugin.cs b/MediaBrowser.Common/Plugins/LocalPlugin.cs index 23b6cfa81..12a1ad1ec 100644 --- a/MediaBrowser.Common/Plugins/LocalPlugin.cs +++ b/MediaBrowser.Common/Plugins/LocalPlugin.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Common.Plugins public LocalPlugin(string path, bool isSupported, PluginManifest manifest) { Path = path; - DllFiles = new List<string>(); + DllFiles = Array.Empty<string>(); _supported = isSupported; Manifest = manifest; } @@ -59,9 +59,9 @@ namespace MediaBrowser.Common.Plugins public string Path { get; } /// <summary> - /// Gets the list of dll files for this plugin. + /// Gets or sets the list of dll files for this plugin. /// </summary> - public List<string> DllFiles { get; } + public IReadOnlyList<string> DllFiles { get; set; } /// <summary> /// Gets or sets the instance of this plugin. diff --git a/MediaBrowser.Common/Progress/ActionableProgress.cs b/MediaBrowser.Common/Progress/ActionableProgress.cs index d5bcd5be9..fe7cb1078 100644 --- a/MediaBrowser.Common/Progress/ActionableProgress.cs +++ b/MediaBrowser.Common/Progress/ActionableProgress.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable CA1003 using System; diff --git a/MediaBrowser.Common/Progress/SimpleProgress.cs b/MediaBrowser.Common/Progress/SimpleProgress.cs index d75675bf1..988d8ad34 100644 --- a/MediaBrowser.Common/Progress/SimpleProgress.cs +++ b/MediaBrowser.Common/Progress/SimpleProgress.cs @@ -1,4 +1,5 @@ #pragma warning disable CS1591 +#pragma warning disable CA1003 using System; diff --git a/MediaBrowser.Common/Providers/ProviderIdParsers.cs b/MediaBrowser.Common/Providers/ProviderIdParsers.cs new file mode 100644 index 000000000..64c2e1976 --- /dev/null +++ b/MediaBrowser.Common/Providers/ProviderIdParsers.cs @@ -0,0 +1,125 @@ +#nullable enable + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace MediaBrowser.Common.Providers +{ + /// <summary> + /// Parsers for provider ids. + /// </summary> + public static class ProviderIdParsers + { + private const int ImdbMinNumbers = 7; + private const int ImdbMaxNumbers = 8; + private const string ImdbPrefix = "tt"; + + /// <summary> + /// Parses an IMDb id from a string. + /// </summary> + /// <param name="text">The text to parse.</param> + /// <param name="imdbId">The parsed IMDb id.</param> + /// <returns>True if parsing was successful, false otherwise.</returns> + public static bool TryFindImdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> imdbId) + { + // imdb id is at least 9 chars (tt + 7 numbers) + while (text.Length >= 2 + ImdbMinNumbers) + { + var ttPos = text.IndexOf(ImdbPrefix); + if (ttPos == -1) + { + imdbId = default; + return false; + } + + text = text.Slice(ttPos); + var i = 2; + var limit = Math.Min(text.Length, ImdbMaxNumbers + 2); + for (; i < limit; i++) + { + var c = text[i]; + if (!IsDigit(c)) + { + break; + } + } + + // skip if more than 8 digits + 2 chars for tt + if (i <= ImdbMaxNumbers + 2 && i >= ImdbMinNumbers + 2) + { + imdbId = text.Slice(0, i); + return true; + } + + text = text.Slice(i); + } + + imdbId = default; + return false; + } + + /// <summary> + /// Parses an TMDb id from a movie url. + /// </summary> + /// <param name="text">The text with the url to parse.</param> + /// <param name="tmdbId">The parsed TMDb id.</param> + /// <returns>True if parsing was successful, false otherwise.</returns> + public static bool TryFindTmdbMovieId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId) + => TryFindProviderId(text, "themoviedb.org/movie/", out tmdbId); + + /// <summary> + /// Parses an TMDb id from a series url. + /// </summary> + /// <param name="text">The text with the url to parse.</param> + /// <param name="tmdbId">The parsed TMDb id.</param> + /// <returns>True if parsing was successful, false otherwise.</returns> + public static bool TryFindTmdbSeriesId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tmdbId) + => TryFindProviderId(text, "themoviedb.org/tv/", out tmdbId); + + /// <summary> + /// Parses an TVDb id from a url. + /// </summary> + /// <param name="text">The text with the url to parse.</param> + /// <param name="tvdbId">The parsed TVDb id.</param> + /// <returns>True if parsing was successful, false otherwise.</returns> + public static bool TryFindTvdbId(ReadOnlySpan<char> text, [NotNullWhen(true)] out ReadOnlySpan<char> tvdbId) + => TryFindProviderId(text, "thetvdb.com/?tab=series&id=", out tvdbId); + + private static bool TryFindProviderId(ReadOnlySpan<char> text, ReadOnlySpan<char> searchString, [NotNullWhen(true)] out ReadOnlySpan<char> providerId) + { + var searchPos = text.IndexOf(searchString); + if (searchPos == -1) + { + providerId = default; + return false; + } + + text = text.Slice(searchPos + searchString.Length); + + int i = 0; + for (; i < text.Length; i++) + { + var c = text[i]; + + if (!IsDigit(c)) + { + break; + } + } + + if (i >= 1) + { + providerId = text.Slice(0, i); + return true; + } + + providerId = default; + return false; + } + + private static bool IsDigit(char c) + { + return c >= '0' && c <= '9'; + } + } +} |
