diff --git a/changelog.d/exif-heic-gps.security b/changelog.d/exif-heic-gps.security new file mode 100644 index 000000000..0ca439d0c --- /dev/null +++ b/changelog.d/exif-heic-gps.security @@ -0,0 +1 @@ +Strip GPS data from WebP and HEIC/AVIF uploads; the Exiftool.StripLocation filter used to skip them diff --git a/changelog.d/exif-stripper.change b/changelog.d/exif-stripper.change new file mode 100644 index 000000000..ba8d47a19 --- /dev/null +++ b/changelog.d/exif-stripper.change @@ -0,0 +1 @@ +Exiftool upload filters are replaced by the built-in Exif.StripLocation and Exif.ReadDescription filters; exiftool is no longer required, old filter names still work but are deprecated diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 87ecb7e2d..d9f4eb1d0 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -168,8 +168,6 @@ defmodule Pleroma.ApplicationRequirements do defp check_system_commands!(:ok) do filter_commands_statuses = [ - check_filter(Pleroma.Upload.Filter.Exiftool.StripLocation, "exiftool"), - check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"), check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"), check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"), check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "ffprobe") diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex index 934a4a02e..b383b646c 100644 --- a/lib/pleroma/config/deprecation_warnings.ex +++ b/lib/pleroma/config/deprecation_warnings.ex @@ -22,40 +22,44 @@ defmodule Pleroma.Config.DeprecationWarnings do @logger_formatter_config_knobs [:colors, :format, :metadata, :truncate, :utc_log] + @exiftool_filter_replacements %{ + Pleroma.Upload.Filter.Exiftool => Pleroma.Upload.Filter.Exif.StripLocation, + Pleroma.Upload.Filter.Exiftool.StripLocation => Pleroma.Upload.Filter.Exif.StripLocation, + Pleroma.Upload.Filter.Exiftool.ReadDescription => Pleroma.Upload.Filter.Exif.ReadDescription + } + def check_exiftool_filter do filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, []) - if Pleroma.Upload.Filter.Exiftool in filters do - Logger.warning(""" - !!!DEPRECATION WARNING!!! - Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later: + deprecated_filters = + filters + |> Enum.filter(&Map.has_key?(@exiftool_filter_replacements, &1)) + |> Enum.uniq() - ``` - config :pleroma, Pleroma.Upload, - filters: [Pleroma.Upload.Filter.Exiftool] - ``` + case deprecated_filters do + [] -> + :ok - Is now + deprecated_filters -> + replacements = + Enum.map_join(deprecated_filters, fn filter -> + "\n* `#{inspect(filter)}` is now `#{inspect(@exiftool_filter_replacements[filter])}`" + end) + Logger.warning(""" + !!!DEPRECATION WARNING!!! + Your config is using Exiftool upload filters. Pleroma no longer needs exiftool, the filters were replaced by built-in ones. The old names should work for now, but you are advised to change to the new configuration to prevent possible issues later: + #{replacements} + """) - ``` - config :pleroma, Pleroma.Upload, - filters: [Pleroma.Upload.Filter.Exiftool.StripLocation] - ``` - """) + new_config = + filters + |> Enum.map(&Map.get(@exiftool_filter_replacements, &1, &1)) + |> Enum.uniq() - new_config = - filters - |> Enum.map(fn - Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripLocation - filter -> filter - end) + Config.put([Pleroma.Upload, :filters], new_config) - Config.put([Pleroma.Upload, :filters], new_config) - - :error - else - :ok + :error end end diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool.ex new file mode 100644 index 000000000..869ac8b2b --- /dev/null +++ b/lib/pleroma/upload/filter/exiftool.ex @@ -0,0 +1,14 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2026 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.Exiftool do + @moduledoc """ + Deprecated: use `Pleroma.Upload.Filter.Exif.StripLocation`. This module delegates to it. + """ + + @behaviour Pleroma.Upload.Filter + + @impl true + defdelegate filter(upload), to: Pleroma.Upload.Filter.Exif.StripLocation +end diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex index 8283b8643..c1c60dfdc 100644 --- a/lib/pleroma/upload/filter/exiftool/read_description.ex +++ b/lib/pleroma/upload/filter/exiftool/read_description.ex @@ -4,51 +4,11 @@ defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do @moduledoc """ - Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet. - It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract. - A valid description means the fields are filled in and not too long (see `:instance, :description_limit`). + Deprecated: use `Pleroma.Upload.Filter.Exif.ReadDescription`. This module delegates to it. """ + @behaviour Pleroma.Upload.Filter - def filter(%Pleroma.Upload{description: description}) - when is_binary(description), - do: {:ok, :noop} - - def filter(%Pleroma.Upload{tempfile: file} = upload), - do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))} - - def filter(_, _), do: {:ok, :noop} - - defp read_description_from_exif_data(file) do - nil - |> read_when_empty(file, "-ImageDescription") - |> read_when_empty(file, "-iptc:Caption-Abstract") - end - - defp read_when_empty(current_description, _, _) when is_binary(current_description), - do: current_description - - defp read_when_empty(_, file, tag) do - if File.exists?(file) do - try do - {tag_content, 0} = - System.cmd("exiftool", ["-m", "-b", "-s3", tag, file], - stderr_to_stdout: false, - parallelism: true - ) - - tag_content = String.trim(tag_content) - - if tag_content != "" and - String.length(tag_content) <= - Pleroma.Config.get([:instance, :description_limit]), - do: tag_content, - else: nil - rescue - _ in ErlangError -> nil - end - else - nil - end - end + @impl true + defdelegate filter(upload), to: Pleroma.Upload.Filter.Exif.ReadDescription end diff --git a/lib/pleroma/upload/filter/exiftool/strip_location.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex index 23346d234..3e64adca8 100644 --- a/lib/pleroma/upload/filter/exiftool/strip_location.ex +++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex @@ -4,30 +4,11 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do @moduledoc """ - Strips GPS related EXIF tags and overwrites the file in place. - Also strips or replaces filesystem metadata e.g., timestamps. + Deprecated: use `Pleroma.Upload.Filter.Exif.StripLocation`. This module delegates to it. """ + @behaviour Pleroma.Upload.Filter - # Formats not compatible with exiftool at this time - def filter(%Pleroma.Upload{content_type: "image/heic"}), do: {:ok, :noop} - def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop} - def filter(%Pleroma.Upload{content_type: "image/svg" <> _}), do: {:ok, :noop} - - def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do - try do - case System.cmd("exiftool", ["-m", "-overwrite_original", "-gps:all=", "-png:all=", file], - stderr_to_stdout: true, - parallelism: true - ) do - {_response, 0} -> {:ok, :filtered} - {error, _} -> {:error, error} - end - rescue - e in ErlangError -> - {:error, "#{__MODULE__}: #{inspect(e)}"} - end - end - - def filter(_), do: {:ok, :noop} + @impl true + defdelegate filter(upload), to: Pleroma.Upload.Filter.Exif.StripLocation end diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs index 62f5bdeea..65b6a3d98 100644 --- a/test/pleroma/config/deprecation_warnings_test.exs +++ b/test/pleroma/config/deprecation_warnings_test.exs @@ -13,40 +13,39 @@ defmodule Pleroma.Config.DeprecationWarningsTest do describe "filter exiftool" do test "gives warning when still used" do - clear_config( - [Pleroma.Upload, :filters], - [Pleroma.Upload.Filter.Exiftool] - ) - - assert capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) =~ - """ - !!!DEPRECATION WARNING!!! - Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later: - - ``` - config :pleroma, Pleroma.Upload, - filters: [Pleroma.Upload.Filter.Exiftool] - ``` - - Is now - - - ``` - config :pleroma, Pleroma.Upload, - filters: [Pleroma.Upload.Filter.Exiftool.StripLocation] - ``` - """ - end - - test "changes setting to exiftool strip location" do clear_config( [Pleroma.Upload, :filters], [Pleroma.Upload.Filter.Exiftool, Pleroma.Upload.Filter.Exiftool.ReadDescription] ) + log = capture_log(fn -> DeprecationWarnings.check_exiftool_filter() end) + + assert log =~ "!!!DEPRECATION WARNING!!!" + + assert log =~ + "* `Pleroma.Upload.Filter.Exiftool` is now `Pleroma.Upload.Filter.Exif.StripLocation`" + + assert log =~ + "* `Pleroma.Upload.Filter.Exiftool.ReadDescription` is now `Pleroma.Upload.Filter.Exif.ReadDescription`" + + refute log =~ "Exiftool.StripLocation`" + end + + test "changes setting to the Exif filters" do + clear_config( + [Pleroma.Upload, :filters], + [ + Pleroma.Upload.Filter.Dedupe, + Pleroma.Upload.Filter.Exiftool, + Pleroma.Upload.Filter.Exiftool.StripLocation, + Pleroma.Upload.Filter.Exiftool.ReadDescription + ] + ) + expected_config = [ - Pleroma.Upload.Filter.Exiftool.StripLocation, - Pleroma.Upload.Filter.Exiftool.ReadDescription + Pleroma.Upload.Filter.Dedupe, + Pleroma.Upload.Filter.Exif.StripLocation, + Pleroma.Upload.Filter.Exif.ReadDescription ] capture_log(fn -> DeprecationWarnings.warn() end) @@ -58,8 +57,8 @@ defmodule Pleroma.Config.DeprecationWarningsTest do clear_config( [Pleroma.Upload, :filters], [ - Pleroma.Upload.Filter.Exiftool.StripLocation, - Pleroma.Upload.Filter.Exiftool.ReadDescription + Pleroma.Upload.Filter.Exif.StripLocation, + Pleroma.Upload.Filter.Exif.ReadDescription ] ) diff --git a/test/pleroma/upload/filter/exiftool/strip_location_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs deleted file mode 100644 index 485060215..000000000 --- a/test/pleroma/upload/filter/exiftool/strip_location_test.exs +++ /dev/null @@ -1,51 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Upload.Filter.Exiftool.StripLocationTest do - use Pleroma.DataCase, async: true - alias Pleroma.Upload.Filter - - test "apply exiftool filter" do - assert Pleroma.Utils.command_available?("exiftool") - - ~w{jpg png} - |> Enum.map(fn type -> - File.cp!( - "test/fixtures/DSCN0010.#{type}", - "test/fixtures/DSCN0010_tmp.#{type}" - ) - - upload = %Pleroma.Upload{ - name: "image_with_GPS_data.#{type}", - content_type: "image/jpeg", - path: Path.absname("test/fixtures/DSCN0010.#{type}"), - tempfile: Path.absname("test/fixtures/DSCN0010_tmp.#{type}") - } - - assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :filtered} - - {exif_original, 0} = System.cmd("exiftool", ["-m", "test/fixtures/DSCN0010.#{type}"]) - {exif_filtered, 0} = System.cmd("exiftool", ["-m", "test/fixtures/DSCN0010_tmp.#{type}"]) - - assert String.match?(exif_original, ~r/GPS/) - refute String.match?(exif_filtered, ~r/GPS/) - end) - end - - test "verify webp, heic, svg files are skipped" do - uploads = - ~w{webp heic svg svg+xml} - |> Enum.map(fn type -> - %Pleroma.Upload{ - name: "sample.#{type}", - content_type: "image/#{type}" - } - end) - - uploads - |> Enum.each(fn upload -> - assert Filter.Exiftool.StripLocation.filter(upload) == {:ok, :noop} - end) - end -end diff --git a/test/pleroma/upload/filter/exiftool_test.exs b/test/pleroma/upload/filter/exiftool_test.exs new file mode 100644 index 000000000..c3ee3c59c --- /dev/null +++ b/test/pleroma/upload/filter/exiftool_test.exs @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2026 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Upload.Filter.ExiftoolTest do + # Instances may still configure the deprecated Exiftool filter names. + # They must keep working, including for formats exiftool used to skip. + use Pleroma.DataCase, async: true + + import Pleroma.Tests.ExifHelpers + + for filter <- [Pleroma.Upload.Filter.Exiftool, Pleroma.Upload.Filter.Exiftool.StripLocation] do + test "#{inspect(filter)} strips GPS data from HEIC uploads" do + upload = upload_copy("strip_location_gps.heic", "image/heic") + assert gps_tags(upload.tempfile) =~ "GPS Latitude" + + assert Pleroma.Upload.Filter.filter([unquote(filter)], upload) == {:ok, upload} + assert gps_tags(upload.tempfile) == "" + end + end + + test "Exiftool.ReadDescription reads the description" do + upload = upload_copy("strip_location_gps.heic", "image/heic", description: nil) + + assert {:ok, %{description: "a heic with a location"}} = + Pleroma.Upload.Filter.filter( + [Pleroma.Upload.Filter.Exiftool.ReadDescription], + upload + ) + end +end