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