From 44535581e269b34643f41b23257c72e0e0daa83a Mon Sep 17 00:00:00 2001 From: Kieran Eglin Date: Sat, 6 Apr 2024 10:56:13 -0700 Subject: [PATCH] Properly escaped NFO file contents --- lib/pinchflat/metadata/nfo_builder.ex | 20 +++++++------- lib/pinchflat/podcasts/rss_feed_builder.ex | 10 ++----- lib/pinchflat/utils/xml_utils.ex | 17 ++++++++++++ test/pinchflat/metadata/nfo_builder_test.exs | 28 ++++++++++++++++++++ test/pinchflat/utils/xml_utils_test.exs | 16 +++++++++++ 5 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 lib/pinchflat/utils/xml_utils.ex create mode 100644 test/pinchflat/utils/xml_utils_test.exs diff --git a/lib/pinchflat/metadata/nfo_builder.ex b/lib/pinchflat/metadata/nfo_builder.ex index fb5ba7b..ad42331 100644 --- a/lib/pinchflat/metadata/nfo_builder.ex +++ b/lib/pinchflat/metadata/nfo_builder.ex @@ -4,6 +4,8 @@ defmodule Pinchflat.Metadata.NfoBuilder do use by Kodi/Jellyfin and other media center software. """ + import Pinchflat.Utils.XmlUtils, only: [safe: 1] + alias Pinchflat.Metadata.MetadataFileHelpers alias Pinchflat.Filesystem.FilesystemHelpers @@ -42,12 +44,12 @@ defmodule Pinchflat.Metadata.NfoBuilder do """ - #{metadata["title"]} - #{metadata["uploader"]} - #{metadata["id"]} - #{metadata["description"]} - #{upload_date} - #{upload_date.year} + #{safe(metadata["title"])} + #{safe(metadata["uploader"])} + #{safe(metadata["id"])} + #{safe(metadata["description"])} + #{safe(upload_date)} + #{safe(upload_date.year)} #{Calendar.strftime(upload_date, "%m%d")} YouTube @@ -58,9 +60,9 @@ defmodule Pinchflat.Metadata.NfoBuilder do """ - #{metadata["title"]} - #{metadata["description"]} - #{metadata["id"]} + #{safe(metadata["title"])} + #{safe(metadata["description"])} + #{safe(metadata["id"])} YouTube """ diff --git a/lib/pinchflat/podcasts/rss_feed_builder.ex b/lib/pinchflat/podcasts/rss_feed_builder.ex index 6a1eb37..5e23edd 100644 --- a/lib/pinchflat/podcasts/rss_feed_builder.ex +++ b/lib/pinchflat/podcasts/rss_feed_builder.ex @@ -5,6 +5,8 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do @datetime_format "%a, %d %b %Y %H:%M:%S %z" + import Pinchflat.Utils.XmlUtils, only: [safe: 1] + alias Pinchflat.Utils.DatetimeUtils alias Pinchflat.Podcasts.PodcastHelpers alias PinchflatWeb.Router.Helpers, as: Routes @@ -94,14 +96,6 @@ defmodule Pinchflat.Podcasts.RssFeedBuilder do """ end - defp safe(nil), do: "" - - defp safe(value) do - value - |> Phoenix.HTML.html_escape() - |> Phoenix.HTML.safe_to_string() - end - defp generate_self_link(url_base, source) do Path.join(url_base, "#{podcast_route(:rss_feed, source.uuid)}.xml") end diff --git a/lib/pinchflat/utils/xml_utils.ex b/lib/pinchflat/utils/xml_utils.ex new file mode 100644 index 0000000..6cbabff --- /dev/null +++ b/lib/pinchflat/utils/xml_utils.ex @@ -0,0 +1,17 @@ +defmodule Pinchflat.Utils.XmlUtils do + @moduledoc """ + Utility methods for working with XML documents + """ + + @doc """ + Escapes invalid XML characters in a string + + Returns binary() + """ + def safe(value) do + value + |> to_string() + |> Phoenix.HTML.html_escape() + |> Phoenix.HTML.safe_to_string() + end +end diff --git a/test/pinchflat/metadata/nfo_builder_test.exs b/test/pinchflat/metadata/nfo_builder_test.exs index c6119a4..14ea1fa 100644 --- a/test/pinchflat/metadata/nfo_builder_test.exs +++ b/test/pinchflat/metadata/nfo_builder_test.exs @@ -30,6 +30,21 @@ defmodule Pinchflat.Metadata.NfoBuilderTest do assert String.contains?(nfo, ~S()) assert String.contains?(nfo, "#{metadata["title"]}") end + + test "escapes invalid characters", %{filepath: filepath} do + metadata = %{ + "title" => "hello' & ", + "uploader" => "uploader", + "id" => "id", + "description" => "description", + "upload_date" => "20210101" + } + + result = NfoBuilder.build_and_store_for_media_item(filepath, metadata) + nfo = File.read!(result) + + assert String.contains?(nfo, "hello' & <world>") + end end describe "build_and_store_for_source/2" do @@ -46,5 +61,18 @@ defmodule Pinchflat.Metadata.NfoBuilderTest do assert String.contains?(nfo, ~S()) assert String.contains?(nfo, "#{metadata["title"]}") end + + test "escapes invalid characters", %{filepath: filepath} do + metadata = %{ + "title" => "hello' & ", + "description" => "description", + "id" => "id" + } + + result = NfoBuilder.build_and_store_for_source(filepath, metadata) + nfo = File.read!(result) + + assert String.contains?(nfo, "hello' & <world>") + end end end diff --git a/test/pinchflat/utils/xml_utils_test.exs b/test/pinchflat/utils/xml_utils_test.exs new file mode 100644 index 0000000..883cfc2 --- /dev/null +++ b/test/pinchflat/utils/xml_utils_test.exs @@ -0,0 +1,16 @@ +defmodule Pinchflat.Utils.XmlUtilsTest do + use ExUnit.Case, async: true + + alias Pinchflat.Utils.XmlUtils + + describe "safe/1" do + test "escapes invalid characters" do + assert XmlUtils.safe("hello' & ") == "hello' & <world>" + end + + test "converts input to string" do + assert XmlUtils.safe(42) == "42" + assert XmlUtils.safe(nil) == "" + end + end +end