From dcc7b6dde98fb3015ded1f725c91080314d4037e Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 27 Sep 2026 16:30:43 -0400 Subject: Backport pull request #18096 from jellyfin/release-12.z Decode HTML entities in OMDb responses Original-merge: 8bd7306fc9aeff8996f474f65647ebde7d001577 Merged-by: crobibero Backported-by: Cody Robibero --- .../Omdb/JsonOmdbNotAvailableStringConverter.cs | 7 +++-- .../Plugins/Omdb/OmdbItemProvider.cs | 5 +--- .../Plugins/Omdb/OmdbProvider.cs | 17 ++++++++--- .../Omdb/JsonOmdbConverterTests.cs | 35 ++++++++++++++++++++++ 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs index 7e9befa8c6..0278469807 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/JsonOmdbNotAvailableStringConverter.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Text.Json; using System.Text.Json.Serialization; using Jellyfin.Extensions.Json; @@ -6,7 +7,7 @@ using Jellyfin.Extensions.Json; namespace MediaBrowser.Providers.Plugins.Omdb { /// - /// Converts a string N/A to string.Empty. + /// Converts a string N/A to string.Empty and decodes HTML entities in every other string. /// public class JsonOmdbNotAvailableStringConverter : JsonConverter { @@ -27,7 +28,9 @@ namespace MediaBrowser.Providers.Plugins.Omdb return null; } - return str; + // Some OMDb records are HTML encoded, e.g. the cast of tt0093058 lists "Vincent D'Onofrio". + // Stored verbatim that name is a second person next to the correctly spelled one. + return WebUtility.HtmlDecode(str); } return JsonSerializer.Deserialize(ref reader, options); diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs index 7b245ea5a7..c81ada3e00 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbItemProvider.cs @@ -13,7 +13,6 @@ using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using Jellyfin.Extensions.Json; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -45,9 +44,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb _libraryManager = libraryManager; _omdbProvider = new OmdbProvider(_httpClientFactory, fileSystem, configurationManager); - _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options); - _jsonOptions.Converters.Add(new JsonOmdbNotAvailableStringConverter()); - _jsonOptions.Converters.Add(new JsonOmdbNotAvailableInt32Converter()); + _jsonOptions = OmdbProvider.CreateJsonOptions(); } public string Name => "The Open Movie Database"; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index d51d913caa..60e4001189 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -45,10 +45,19 @@ namespace MediaBrowser.Providers.Plugins.Omdb _fileSystem = fileSystem; _configurationManager = configurationManager; - _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options); - // These converters need to take priority - _jsonOptions.Converters.Insert(0, new JsonOmdbNotAvailableStringConverter()); - _jsonOptions.Converters.Insert(0, new JsonOmdbNotAvailableInt32Converter()); + _jsonOptions = CreateJsonOptions(); + } + + /// Creates the to read OMDB responses with. + /// The options. + internal static JsonSerializerOptions CreateJsonOptions() + { + var options = new JsonSerializerOptions(JsonDefaults.Options); + // These have to be inserted, appending leaves the default string converter ahead of them + options.Converters.Insert(0, new JsonOmdbNotAvailableStringConverter()); + options.Converters.Insert(0, new JsonOmdbNotAvailableInt32Converter()); + + return options; } /// Fetches data from OMDB service. diff --git a/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs index 3062cb7b42..0275d13242 100644 --- a/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs +++ b/tests/Jellyfin.Providers.Tests/Omdb/JsonOmdbConverterTests.cs @@ -63,6 +63,41 @@ namespace Jellyfin.Providers.Tests.Omdb Assert.Equal(expected, result); } + [Theory] + [InlineData("\"Vincent D'Onofrio\"", "Vincent D'Onofrio")] + [InlineData("\"Alan Moore 'Swamp Thing'\"", "Alan Moore 'Swamp Thing'")] + [InlineData("\"Tom & Jerry\"", "Tom & Jerry")] + [InlineData("\"Mail <a@b.com> for a translation\"", "Mail for a translation")] + [InlineData("\"Elisa Mont\\u00e9s\"", "Elisa Montés")] + [InlineData("\"R&D and 100% & more\"", "R&D and 100% & more")] + public void Deserialize_Html_Encoded_String_Is_Decoded(string input, string expected) + { + var result = JsonSerializer.Deserialize(input, _options); + Assert.Equal(expected, result); + } + + [Fact] + public void Deserialize_Html_Encoded_Credits_Are_Decoded() + { + const string Input = "{\"Title\":\"Full Metal Jacket\",\"Actors\":\"Matthew Modine, R. Lee Ermey, Vincent D'Onofrio\",\"Response\":\"True\"}"; + var rootObject = JsonSerializer.Deserialize(Input, _options); + Assert.NotNull(rootObject); + Assert.Equal("Matthew Modine, R. Lee Ermey, Vincent D'Onofrio", rootObject!.Actors); + } + + [Fact] + public void ProviderJsonOptions_Take_Priority_Over_The_Defaults() + { + // The default string converter handles string as well, so appending the OMDb ones would never run them + var options = OmdbProvider.CreateJsonOptions(); + + const string Input = "{\"Title\":\"Full Metal Jacket\",\"Actors\":\"Vincent D'Onofrio\",\"Awards\":\"N/A\",\"Response\":\"True\"}"; + var rootObject = JsonSerializer.Deserialize(Input, options); + Assert.NotNull(rootObject); + Assert.Equal("Vincent D'Onofrio", rootObject!.Actors); + Assert.Null(rootObject.Awards); + } + [Fact] public void Roundtrip_Valid_Success() { -- cgit v1.2.3