From dd337b92f1b561c86b79f7491b08e068a3ac3a6f Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Tue, 27 Oct 2026 17:29:15 +0400 Subject: [PATCH] refactor(upload): make Exiftool filters deprecated aliases of Exif filters Old configs keep working and now also strip GPS from WebP and HEIC. The deprecation warning names the replacement for each old filter, and exiftool is no longer a startup requirement. --- changelog.d/exif-heic-gps.security | 1 + changelog.d/exif-stripper.change | 1 + lib/pleroma/application_requirements.ex | 2 - lib/pleroma/config/deprecation_warnings.ex | 54 +++++++++-------- lib/pleroma/upload/filter/exiftool.ex | 14 +++++ .../filter/exiftool/read_description.ex | 48 ++------------- .../upload/filter/exiftool/strip_location.ex | 27 ++------- .../config/deprecation_warnings_test.exs | 59 +++++++++---------- .../filter/exiftool/strip_location_test.exs | 51 ---------------- test/pleroma/upload/filter/exiftool_test.exs | 31 ++++++++++ 10 files changed, 113 insertions(+), 175 deletions(-) create mode 100644 changelog.d/exif-heic-gps.security create mode 100644 changelog.d/exif-stripper.change create mode 100644 lib/pleroma/upload/filter/exiftool.ex delete mode 100644 test/pleroma/upload/filter/exiftool/strip_location_test.exs create mode 100644 test/pleroma/upload/filter/exiftool_test.exs 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