Merge pull request 'Uploads: replace exiftool with the built-in exif_stripper' (#8028) from lambadalambda/pleroma:feature/exif-stripper into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/8028
This commit is contained in:
commit
3e9dc477c9
44 changed files with 518 additions and 381 deletions
|
|
@ -42,7 +42,7 @@ ARG HOME=/opt/pleroma
|
|||
ARG DATA=/var/lib/pleroma
|
||||
|
||||
RUN apk update &&\
|
||||
apk add exiftool ffmpeg vips libmagic ncurses postgresql-client &&\
|
||||
apk add ffmpeg vips libmagic ncurses postgresql-client &&\
|
||||
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
|
||||
mkdir -p ${DATA}/uploads &&\
|
||||
mkdir -p ${DATA}/static &&\
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ ARG HOME=/opt/pleroma
|
|||
ARG DATA=/var/lib/pleroma
|
||||
|
||||
RUN apk update &&\
|
||||
apk add exiftool ffmpeg vips libmagic ncurses postgresql-client &&\
|
||||
apk add ffmpeg vips libmagic ncurses postgresql-client &&\
|
||||
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
|
||||
mkdir -p ${DATA}/uploads &&\
|
||||
mkdir -p ${DATA}/static &&\
|
||||
|
|
|
|||
1
changelog.d/exif-heic-gps.security
Normal file
1
changelog.d/exif-heic-gps.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Strip GPS data from WebP and HEIC/AVIF uploads; the Exiftool.StripLocation filter used to skip them; GPS data in XMP (including DJI drone positions) is stripped too
|
||||
1
changelog.d/exif-stripper.change
Normal file
1
changelog.d/exif-stripper.change
Normal file
|
|
@ -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
|
||||
|
|
@ -37,8 +37,8 @@ If any of the options are left unspecified, you will be prompted interactively.
|
|||
- `--static-dir <path>` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
||||
- `--listen-ip <ip>` - the ip the app should listen to, defaults to 127.0.0.1
|
||||
- `--listen-port <port>` - the port the app should listen to, defaults to 4000
|
||||
- `--strip-uploads-location <Y|N>` - use ExifTool to strip uploads of sensitive location data
|
||||
- `--read-uploads-description <Y|N>` - use ExifTool to read image descriptions from uploads
|
||||
- `--strip-uploads-location <Y|N>` - strip sensitive location data from uploaded images
|
||||
- `--read-uploads-description <Y|N>` - read image descriptions from upload metadata
|
||||
- `--anonymize-uploads <Y|N>` - randomize uploaded filenames
|
||||
- `--dedupe-uploads <Y|N>` - store files based on their hash to reduce data storage requirements if duplicates are uploaded with different filenames
|
||||
- `--skip-release-env` - skip generation the release environment file
|
||||
|
|
|
|||
|
|
@ -705,15 +705,19 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
|
|||
|
||||
No specific configuration.
|
||||
|
||||
#### Pleroma.Upload.Filter.Exiftool.StripLocation
|
||||
#### Pleroma.Upload.Filter.Exif.StripLocation
|
||||
|
||||
This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
|
||||
This filter strips GPS and location metadata from JPEG, WebP, HEIC, AVIF and TIFF images, leaving color profiles and attributes intact. For PNG images it removes all text and EXIF metadata chunks. Images are not re-encoded, and other formats are left unchanged.
|
||||
|
||||
It replaces the deprecated `Pleroma.Upload.Filter.Exiftool.StripLocation`, which no longer needs exiftool.
|
||||
|
||||
No specific configuration.
|
||||
|
||||
#### Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||
#### Pleroma.Upload.Filter.Exif.ReadDescription
|
||||
|
||||
This filter reads the ImageDescription and iptc:Caption-Abstract fields with Exiftool so clients can prefill the media description field.
|
||||
This filter reads the ImageDescription and iptc:Caption-Abstract fields so clients can prefill the media description field.
|
||||
|
||||
It replaces the deprecated `Pleroma.Upload.Filter.Exiftool.ReadDescription`, which no longer needs exiftool.
|
||||
|
||||
No specific configuration.
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ sudo rc-update add postgresql
|
|||
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
|
||||
|
||||
```shell
|
||||
sudo apk add ffmpeg imagemagick exiftool
|
||||
sudo apk add ffmpeg imagemagick
|
||||
```
|
||||
|
||||
### Install PleromaBE
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ sudo apt install elixir erlang-dev erlang-nox
|
|||
### Optional packages: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
|
||||
|
||||
```shell
|
||||
sudo apt install imagemagick ffmpeg libimage-exiftool-perl
|
||||
sudo apt install imagemagick ffmpeg
|
||||
```
|
||||
|
||||
### Install PleromaBE
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ Note: This article is potentially outdated because at this time we may not have
|
|||
- `certbot` (または何らかのLet's Encrypt向けACMEクライアント)
|
||||
- `ImageMagick`
|
||||
- `ffmpeg`
|
||||
- `exiftool`
|
||||
|
||||
### システムを準備する
|
||||
|
||||
|
|
@ -60,7 +59,7 @@ sudo apt install elixir erlang-dev erlang-nox
|
|||
### オプションパッケージ: [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md)
|
||||
|
||||
```shell
|
||||
sudo apt install imagemagick ffmpeg libimage-exiftool-perl
|
||||
sudo apt install imagemagick ffmpeg
|
||||
```
|
||||
|
||||
### Pleroma BE (バックエンド) をインストールします
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ Setup the required services to automatically start at boot, using `sysrc(8)`.
|
|||
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
|
||||
|
||||
```shell
|
||||
# pkg install imagemagick ffmpeg p5-Image-ExifTool vips
|
||||
# pkg install imagemagick ffmpeg vips
|
||||
```
|
||||
|
||||
## Configuring Pleroma
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
* ImageMagick
|
||||
* FFmpeg
|
||||
* exiftool
|
||||
|
||||
!!!info
|
||||
**Source installs only**
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
|
|||
* `app-crypt/certbot-nginx` (nginx certbot plugin that allows use of the all-powerful `--nginx` flag on certbot)
|
||||
* `media-gfx/imagemagick`
|
||||
* `media-video/ffmpeg`
|
||||
* `media-libs/exiftool`
|
||||
|
||||
### Prepare the system
|
||||
|
||||
|
|
@ -97,7 +96,7 @@ Not only does this make it much easier to deploy changes you make, as you can co
|
|||
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](docs/installation/optional/media_graphics_packages.md))
|
||||
|
||||
```shell
|
||||
# emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool
|
||||
# emerge --ask media-video/ffmpeg media-gfx/imagemagick
|
||||
```
|
||||
|
||||
### Install PleromaBE
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ Gentoo quite pointedly does not come with a cron daemon installed, and as such i
|
|||
* `app-crypt/certbot-nginx` (nginx certbot plugin that allows use of the all-powerful `--nginx` flag on certbot)
|
||||
* `media-gfx/imagemagick`
|
||||
* `media-video/ffmpeg`
|
||||
* `media-libs/exiftool`
|
||||
|
||||
### Prepare the system
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ The output from emerging postgresql should give you a command for initializing t
|
|||
See [Optional software packages needed for specific functionality](optional/media_graphics_packages.md) for details.
|
||||
|
||||
```shell
|
||||
# emerge --ask media-video/ffmpeg media-gfx/imagemagick media-libs/exiftool
|
||||
# emerge --ask media-video/ffmpeg media-gfx/imagemagick
|
||||
```
|
||||
|
||||
### Setup PleromaBE
|
||||
|
|
|
|||
|
|
@ -26,12 +26,11 @@ Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acm
|
|||
|
||||
* ImageMagick
|
||||
* ffmpeg
|
||||
* exiftool
|
||||
|
||||
To install the above:
|
||||
|
||||
```
|
||||
# pkg_add ImageMagick ffmpeg p5-Image-ExifTool
|
||||
# pkg_add ImageMagick ffmpeg
|
||||
```
|
||||
|
||||
For more information read [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md):
|
||||
|
|
|
|||
|
|
@ -25,11 +25,10 @@ Asenna tarvittava ohjelmisto:
|
|||
[`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md):
|
||||
* ImageMagick
|
||||
* ffmpeg
|
||||
* exiftool
|
||||
|
||||
Asenna tarvittava ohjelmisto:
|
||||
|
||||
`# pkg_add ImageMagick ffmpeg p5-Image-ExifTool`
|
||||
`# pkg_add ImageMagick ffmpeg`
|
||||
|
||||
Luo postgresql-tietokanta:
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
For specific Pleroma functionality (which is disabled by default) some or all of the below packages are required:
|
||||
|
||||
* `ImageMagic`
|
||||
* `ImageMagick`
|
||||
* `ffmpeg`
|
||||
* `exiftool`
|
||||
|
||||
Please refer to documentation in `docs/installation` on how to install them on specific OS.
|
||||
|
||||
|
|
@ -26,12 +25,3 @@ It is required for the following Pleroma features:
|
|||
It is required for the following Pleroma features:
|
||||
|
||||
* Media preview proxy for videos (related config: `media_preview_proxy/enabled` in `config/config.exs`)
|
||||
|
||||
## `exiftool`
|
||||
|
||||
`exiftool` is media files metadata reader/writer.
|
||||
|
||||
It is required for the following Pleroma features:
|
||||
|
||||
* `Pleroma.Upload.Filters.Exiftool.StripLocation` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||
* `Pleroma.Upload.Filters.Exiftool.ReadDescription` upload filter (related config: `Pleroma.Upload/filters` in `config/config.exs`)
|
||||
|
|
|
|||
|
|
@ -61,17 +61,16 @@ Other than things bundled in the OTP release Pleroma depends on:
|
|||
Per [`docs/installation/optional/media_graphics_packages.md`](optional/media_graphics_packages.md):
|
||||
* ImageMagick
|
||||
* ffmpeg
|
||||
* exiftool
|
||||
|
||||
=== "Alpine"
|
||||
```
|
||||
apk update
|
||||
apk add imagemagick ffmpeg exiftool
|
||||
apk add imagemagick ffmpeg
|
||||
```
|
||||
|
||||
=== "Debian/Ubuntu"
|
||||
```
|
||||
apt install imagemagick ffmpeg libimage-exiftool-perl
|
||||
apt install imagemagick ffmpeg
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|||
openssl libssl3 \
|
||||
libmagic1t64 file \
|
||||
postgresql-client \
|
||||
ffmpeg imagemagick libimage-exiftool-perl \
|
||||
ffmpeg imagemagick \
|
||||
libvips42t64 \
|
||||
unzip \
|
||||
curl \
|
||||
|
|
|
|||
|
|
@ -162,38 +162,20 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
)
|
||||
|> Path.expand()
|
||||
|
||||
{strip_uploads_location_message, strip_uploads_location_default} =
|
||||
if Pleroma.Utils.command_available?("exiftool") do
|
||||
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
|
||||
"y"}
|
||||
else
|
||||
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
|
||||
"n"}
|
||||
end
|
||||
|
||||
strip_uploads_location =
|
||||
get_option(
|
||||
options,
|
||||
:strip_uploads_location,
|
||||
strip_uploads_location_message,
|
||||
strip_uploads_location_default
|
||||
"Do you want to strip location (GPS) data from uploaded images? (y/n)",
|
||||
"y"
|
||||
) === "y"
|
||||
|
||||
{read_uploads_description_message, read_uploads_description_default} =
|
||||
if Pleroma.Utils.command_available?("exiftool") do
|
||||
{"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
|
||||
"y"}
|
||||
else
|
||||
{"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
|
||||
"n"}
|
||||
end
|
||||
|
||||
read_uploads_description =
|
||||
get_option(
|
||||
options,
|
||||
:read_uploads_description,
|
||||
read_uploads_description_message,
|
||||
read_uploads_description_default
|
||||
"Do you want to read metadata from uploaded images so clients can prefill the media description field? (y/n)",
|
||||
"y"
|
||||
) === "y"
|
||||
|
||||
anonymize_uploads =
|
||||
|
|
@ -324,14 +306,14 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
defp upload_filters(filters) when is_map(filters) do
|
||||
enabled_filters =
|
||||
if filters.strip_location do
|
||||
[Pleroma.Upload.Filter.Exiftool.StripLocation]
|
||||
[Pleroma.Upload.Filter.Exif.StripLocation]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
enabled_filters =
|
||||
if filters.read_description do
|
||||
enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
|
||||
enabled_filters ++ [Pleroma.Upload.Filter.Exif.ReadDescription]
|
||||
else
|
||||
enabled_filters
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
32
lib/pleroma/upload/filter/exif/read_description.ex
Normal file
32
lib/pleroma/upload/filter/exif/read_description.ex
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exif.ReadDescription do
|
||||
@moduledoc """
|
||||
Reads a description from image metadata when the upload has none yet.
|
||||
|
||||
Checks EXIF ImageDescription, then IPTC Caption-Abstract (and XMP for GIF). Blank
|
||||
values and values longer than `[:instance, :description_limit]` are ignored.
|
||||
"""
|
||||
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
|
||||
@impl true
|
||||
def filter(%Pleroma.Upload{description: description}) when is_binary(description),
|
||||
do: {:ok, :noop}
|
||||
|
||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _} = upload) do
|
||||
max_length = Pleroma.Config.get([:instance, :description_limit])
|
||||
|
||||
case ExifStripper.Description.read_file(file, max_length: max_length) do
|
||||
description when is_binary(description) ->
|
||||
{:ok, :filtered, %{upload | description: description}}
|
||||
|
||||
nil ->
|
||||
{:ok, :noop}
|
||||
end
|
||||
end
|
||||
|
||||
def filter(_), do: {:ok, :noop}
|
||||
end
|
||||
25
lib/pleroma/upload/filter/exif/strip_location.ex
Normal file
25
lib/pleroma/upload/filter/exif/strip_location.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exif.StripLocation do
|
||||
@moduledoc """
|
||||
Strips GPS metadata from uploaded images and overwrites the file in place.
|
||||
|
||||
Supports JPEG, PNG, WebP, TIFF and ISO-BMFF images (HEIC, AVIF). This is lossless:
|
||||
the image data is not re-encoded. Other image types are left untouched.
|
||||
"""
|
||||
|
||||
@behaviour Pleroma.Upload.Filter
|
||||
|
||||
@impl true
|
||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||
case ExifStripper.GpsStripper.strip_gps_data(file, file) do
|
||||
:ok -> {:ok, :filtered}
|
||||
:noop -> {:ok, :noop}
|
||||
{:error, _} = error -> error
|
||||
end
|
||||
end
|
||||
|
||||
def filter(_), do: {:ok, :noop}
|
||||
end
|
||||
14
lib/pleroma/upload/filter/exiftool.ex
Normal file
14
lib/pleroma/upload/filter/exiftool.ex
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
mix.exs
1
mix.exs
|
|
@ -205,6 +205,7 @@ defmodule Pleroma.Mixfile do
|
|||
{:majic, "~> 1.2"},
|
||||
{:open_api_spex, "~> 3.22"},
|
||||
{:ecto_psql_extras, "~> 0.8"},
|
||||
{:exif_stripper, "~> 0.1.3"},
|
||||
{:vix, "~> 0.36"},
|
||||
{:elixir_make, "~> 0.7.8", override: true},
|
||||
{:blurhash, "~> 0.1.0", hex: :rinpatch_blurhash},
|
||||
|
|
|
|||
1
mix.lock
1
mix.lock
|
|
@ -47,6 +47,7 @@
|
|||
"ex_doc": {:hex, :ex_doc, "0.38.2", "504d25eef296b4dec3b8e33e810bc8b5344d565998cd83914ffe1b8503737c02", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "732f2d972e42c116a70802f9898c51b54916e542cc50968ac6980512ec90f42b"},
|
||||
"ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
|
||||
"ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"},
|
||||
"exif_stripper": {:hex, :exif_stripper, "0.1.3", "b3d89a623bb49c09fbe45fe5d4f1becee0ffe011bcade174c3bb91cdae59745e", [:mix], [], "hexpm", "e1ab26d766205ae54be57c19ec70d408b020f3c31855b49d53a55460de81c6e1"},
|
||||
"exile": {:hex, :exile, "0.10.0", "b69e2d27a9af670b0f0a0898addca0eda78f6f5ba95ccfbc9bc6ccdd04925436", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c62ee8fee565b5ac4a898d0dcd58d2b04fb5eec1655af1ddcc9eb582c6732c33"},
|
||||
"expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"},
|
||||
"fast_html": {:hex, :fast_html, "2.5.0", "23578c1c1fa03db6a3786d78218b2d944df34b0fc3729e72de912f390913c80f", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "69eb46ed98a5d9cca1ccd4a5ac94ce5dd626fc29513fbaa0a16cd8b2da67ae3e"},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExif do
|
||||
use Ecto.Migration
|
||||
|
||||
alias Pleroma.ConfigDB
|
||||
|
||||
@up %{
|
||||
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
|
||||
}
|
||||
|
||||
@down %{
|
||||
Pleroma.Upload.Filter.Exif.StripLocation => Pleroma.Upload.Filter.Exiftool.StripLocation,
|
||||
Pleroma.Upload.Filter.Exif.ReadDescription => Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||
}
|
||||
|
||||
def up, do: rename_filters(@up)
|
||||
|
||||
def down, do: rename_filters(@down)
|
||||
|
||||
# Only touch configs that list filters: adding an empty list would hide the
|
||||
# filters from the config file.
|
||||
defp rename_filters(renames) do
|
||||
with %{value: value} <- ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload}),
|
||||
filters when is_list(filters) <- value[:filters],
|
||||
new_filters when new_filters != filters <-
|
||||
filters |> Enum.map(&Map.get(renames, &1, &1)) |> Enum.uniq() do
|
||||
ConfigDB.update_or_create(%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Upload,
|
||||
value: Keyword.put(value, :filters, new_filters)
|
||||
})
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
end
|
||||
BIN
test/fixtures/dji_gps.jpg
vendored
Normal file
BIN
test/fixtures/dji_gps.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
BIN
test/fixtures/strip_location_gps.heic
vendored
Normal file
BIN
test/fixtures/strip_location_gps.heic
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/strip_location_gps.tiff
vendored
Normal file
BIN
test/fixtures/strip_location_gps.tiff
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/strip_location_gps.webp
vendored
Normal file
BIN
test/fixtures/strip_location_gps.webp
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
test/fixtures/strip_location_gps_grid.heic
vendored
Normal file
BIN
test/fixtures/strip_location_gps_grid.heic
vendored
Normal file
Binary file not shown.
BIN
test/fixtures/xmp_gps.jpg
vendored
Normal file
BIN
test/fixtures/xmp_gps.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -95,7 +95,7 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
|
|||
assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
|
||||
|
||||
assert generated_config =~
|
||||
"filters: [Pleroma.Upload.Filter.Exiftool.StripLocation, Pleroma.Upload.Filter.Exiftool.ReadDescription]"
|
||||
"filters: [Pleroma.Upload.Filter.Exif.StripLocation, Pleroma.Upload.Filter.Exif.ReadDescription]"
|
||||
|
||||
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
||||
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Repo.Migrations.UploadFilterExiftoolToExifTest do
|
||||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
import Pleroma.Tests.Helpers
|
||||
alias Pleroma.ConfigDB
|
||||
|
||||
setup_all do: require_migration("20261027000000_upload_filter_exiftool_to_exif")
|
||||
|
||||
defp filters do
|
||||
ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload}).value[:filters]
|
||||
end
|
||||
|
||||
test "up/0 renames the Exiftool filters", %{migration: migration} do
|
||||
insert(:config,
|
||||
group: :pleroma,
|
||||
key: Pleroma.Upload,
|
||||
value: [
|
||||
filters: [
|
||||
Pleroma.Upload.Filter.Dedupe,
|
||||
Pleroma.Upload.Filter.Exiftool,
|
||||
Pleroma.Upload.Filter.Exiftool.StripLocation,
|
||||
Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
migration.up()
|
||||
|
||||
assert filters() == [
|
||||
Pleroma.Upload.Filter.Dedupe,
|
||||
Pleroma.Upload.Filter.Exif.StripLocation,
|
||||
Pleroma.Upload.Filter.Exif.ReadDescription
|
||||
]
|
||||
end
|
||||
|
||||
test "down/0 restores the Exiftool filters", %{migration: migration} do
|
||||
insert(:config,
|
||||
group: :pleroma,
|
||||
key: Pleroma.Upload,
|
||||
value: [
|
||||
filters: [
|
||||
Pleroma.Upload.Filter.Exif.StripLocation,
|
||||
Pleroma.Upload.Filter.Exif.ReadDescription
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
migration.down()
|
||||
|
||||
assert filters() == [
|
||||
Pleroma.Upload.Filter.Exiftool.StripLocation,
|
||||
Pleroma.Upload.Filter.Exiftool.ReadDescription
|
||||
]
|
||||
end
|
||||
|
||||
test "up/0 does nothing without a database config", %{migration: migration} do
|
||||
assert migration.up() == nil
|
||||
assert ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload}) == nil
|
||||
end
|
||||
|
||||
test "up/0 does not add filters to a database config without them", %{migration: migration} do
|
||||
insert(:config, group: :pleroma, key: Pleroma.Upload, value: [uploader: Pleroma.Uploaders.S3])
|
||||
|
||||
migration.up()
|
||||
|
||||
value = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Upload}).value
|
||||
assert value == [uploader: Pleroma.Uploaders.S3]
|
||||
end
|
||||
end
|
||||
85
test/pleroma/upload/filter/exif/read_description_test.exs
Normal file
85
test/pleroma/upload/filter/exif/read_description_test.exs
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exif.ReadDescriptionTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
alias Pleroma.Upload.Filter.Exif.ReadDescription
|
||||
|
||||
defp upload(fixture, attrs \\ []) do
|
||||
path = Path.absname(Path.join("test/fixtures", fixture))
|
||||
|
||||
struct!(
|
||||
%Pleroma.Upload{
|
||||
name: fixture,
|
||||
content_type: "image/" <> String.trim_leading(Path.extname(fixture), "."),
|
||||
path: path,
|
||||
tempfile: path,
|
||||
description: nil
|
||||
},
|
||||
attrs
|
||||
)
|
||||
end
|
||||
|
||||
test "keeps description when not empty" do
|
||||
upload =
|
||||
upload("image_with_imagedescription_and_caption-abstract.jpg",
|
||||
description: "Some description"
|
||||
)
|
||||
|
||||
assert ReadDescription.filter(upload) == {:ok, :noop}
|
||||
end
|
||||
|
||||
test "otherwise returns ImageDescription when present" do
|
||||
upload = upload("image_with_imagedescription_and_caption-abstract.jpg")
|
||||
|
||||
assert ReadDescription.filter(upload) ==
|
||||
{:ok, :filtered, %{upload | description: "a descriptive white pixel"}}
|
||||
end
|
||||
|
||||
test "ignores stray data after the image" do
|
||||
upload = upload("image_with_imagedescription_and_caption-abstract_and_stray_data_after.png")
|
||||
|
||||
assert {:ok, :filtered, %{description: "a descriptive white pixel"}} =
|
||||
ReadDescription.filter(upload)
|
||||
|
||||
assert ReadDescription.filter(upload("image_with_stray_data_after.png")) == {:ok, :noop}
|
||||
end
|
||||
|
||||
test "otherwise returns iptc:Caption-Abstract when present" do
|
||||
upload = upload("image_with_caption-abstract.jpg")
|
||||
|
||||
assert ReadDescription.filter(upload) ==
|
||||
{:ok, :filtered, %{upload | description: "an abstract white pixel"}}
|
||||
end
|
||||
|
||||
test "reads ImageDescription from WebP and HEIC" do
|
||||
assert {:ok, :filtered, %{description: "a webp with a location"}} =
|
||||
ReadDescription.filter(upload("strip_location_gps.webp"))
|
||||
|
||||
assert {:ok, :filtered, %{description: "a heic with a location"}} =
|
||||
ReadDescription.filter(upload("strip_location_gps.heic"))
|
||||
end
|
||||
|
||||
test "does nothing when there is no description" do
|
||||
assert ReadDescription.filter(upload("image_with_no_description.jpg")) == {:ok, :noop}
|
||||
end
|
||||
|
||||
test "ignores descriptions that exceed the maximum length" do
|
||||
clear_config([:instance, :description_limit], 5)
|
||||
|
||||
upload = upload("image_with_imagedescription_and_caption-abstract.jpg")
|
||||
assert ReadDescription.filter(upload) == {:ok, :noop}
|
||||
end
|
||||
|
||||
test "ignores content with only whitespace" do
|
||||
upload = upload("image_with_imagedescription_and_caption-abstract_whitespaces.jpg")
|
||||
assert ReadDescription.filter(upload) == {:ok, :noop}
|
||||
end
|
||||
|
||||
test "does nothing when the file can't be read" do
|
||||
upload = %{upload("image.jpg") | tempfile: Path.absname("test/fixtures/non-existant_tmp.jpg")}
|
||||
assert ReadDescription.filter(upload) == {:ok, :noop}
|
||||
end
|
||||
end
|
||||
72
test/pleroma/upload/filter/exif/strip_location_test.exs
Normal file
72
test/pleroma/upload/filter/exif/strip_location_test.exs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exif.StripLocationTest do
|
||||
use Pleroma.DataCase, async: true
|
||||
|
||||
import Pleroma.Tests.ExifHelpers
|
||||
|
||||
alias Pleroma.Upload.Filter.Exif.StripLocation
|
||||
|
||||
for {fixture, content_type} <- [
|
||||
{"DSCN0010.jpg", "image/jpeg"},
|
||||
{"DSCN0010.png", "image/png"},
|
||||
{"strip_location_gps.webp", "image/webp"},
|
||||
{"strip_location_gps.heic", "image/heic"},
|
||||
{"strip_location_gps_grid.heic", "image/heic"},
|
||||
{"strip_location_gps.tiff", "image/tiff"}
|
||||
] do
|
||||
test "strips GPS data from #{fixture} (#{content_type})" do
|
||||
upload = upload_copy(unquote(fixture), unquote(content_type))
|
||||
assert gps_tags(upload.tempfile) =~ "GPS Latitude"
|
||||
|
||||
assert StripLocation.filter(upload) == {:ok, :filtered}
|
||||
assert gps_tags(upload.tempfile) == ""
|
||||
end
|
||||
end
|
||||
|
||||
test "strips GPS data from XMP, including DJI drone positions" do
|
||||
for fixture <- ["xmp_gps.jpg", "dji_gps.jpg"] do
|
||||
upload = upload_copy(fixture, "image/jpeg")
|
||||
assert exiftool_xmp(upload.tempfile) =~ "GPSLatitude"
|
||||
|
||||
assert StripLocation.filter(upload) == {:ok, :filtered}
|
||||
refute exiftool_xmp(upload.tempfile) =~ ~r/GPS|AbsoluteAltitude/
|
||||
end
|
||||
end
|
||||
|
||||
defp exiftool_xmp(path) do
|
||||
{output, 0} = System.cmd("exiftool", ["-m", "-s", "-xmp:all", path])
|
||||
output
|
||||
end
|
||||
|
||||
test "keeps other EXIF data in HEIC" do
|
||||
upload = upload_copy("strip_location_gps.heic", "image/heic")
|
||||
|
||||
assert StripLocation.filter(upload) == {:ok, :filtered}
|
||||
assert tag(upload.tempfile, "ImageDescription") == "a heic with a location"
|
||||
assert tag(upload.tempfile, "ImageSize") == "64x48"
|
||||
end
|
||||
|
||||
test "does nothing when there is no GPS data" do
|
||||
upload = upload_copy("image.jpg", "image/jpeg")
|
||||
original = File.read!(upload.tempfile)
|
||||
|
||||
assert StripLocation.filter(upload) == {:ok, :noop}
|
||||
assert File.read!(upload.tempfile) == original
|
||||
end
|
||||
|
||||
test "accepts PNG files with data after IEND" do
|
||||
upload = upload_copy("image_with_stray_data_after.png", "image/png")
|
||||
|
||||
assert {:ok, _} = StripLocation.filter(upload)
|
||||
end
|
||||
|
||||
test "ignores non-image uploads" do
|
||||
upload = upload_copy("DSCN0010.jpg", "application/octet-stream")
|
||||
|
||||
assert StripLocation.filter(upload) == {:ok, :noop}
|
||||
assert gps_tags(upload.tempfile) =~ "GPS Latitude"
|
||||
end
|
||||
end
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Upload.Filter.Exiftool.ReadDescriptionTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Upload.Filter
|
||||
|
||||
@uploads %Pleroma.Upload{
|
||||
name: "image_with_imagedescription_and_caption-abstract.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||
tempfile: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||
description: nil
|
||||
}
|
||||
|
||||
test "keeps description when not empty" do
|
||||
uploads = %Pleroma.Upload{
|
||||
name: "image_with_imagedescription_and_caption-abstract.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||
tempfile:
|
||||
Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||
description: "Some description"
|
||||
}
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||
{:ok, :noop}
|
||||
end
|
||||
|
||||
test "otherwise returns ImageDescription when present" do
|
||||
uploads_after = %Pleroma.Upload{
|
||||
name: "image_with_imagedescription_and_caption-abstract.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||
tempfile:
|
||||
Path.absname("test/fixtures/image_with_imagedescription_and_caption-abstract.jpg"),
|
||||
description: "a descriptive white pixel"
|
||||
}
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
|
||||
{:ok, :filtered, uploads_after}
|
||||
end
|
||||
|
||||
test "Ignores warnings" do
|
||||
uploads = %Pleroma.Upload{
|
||||
name: "image_with_imagedescription_and_caption-abstract_and_stray_data_after.png",
|
||||
content_type: "image/png",
|
||||
path:
|
||||
Path.absname(
|
||||
"test/fixtures/image_with_imagedescription_and_caption-abstract_and_stray_data_after.png"
|
||||
),
|
||||
tempfile:
|
||||
Path.absname(
|
||||
"test/fixtures/image_with_imagedescription_and_caption-abstract_and_stray_data_after.png"
|
||||
)
|
||||
}
|
||||
|
||||
assert {:ok, :filtered, %{description: "a descriptive white pixel"}} =
|
||||
Filter.Exiftool.ReadDescription.filter(uploads)
|
||||
|
||||
uploads = %Pleroma.Upload{
|
||||
name: "image_with_stray_data_after.png",
|
||||
content_type: "image/png",
|
||||
path: Path.absname("test/fixtures/image_with_stray_data_after.png"),
|
||||
tempfile: Path.absname("test/fixtures/image_with_stray_data_after.png")
|
||||
}
|
||||
|
||||
assert {:ok, :filtered, %{description: nil}} = Filter.Exiftool.ReadDescription.filter(uploads)
|
||||
end
|
||||
|
||||
test "otherwise returns iptc:Caption-Abstract when present" do
|
||||
upload = %Pleroma.Upload{
|
||||
name: "image_with_caption-abstract.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||
tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||
description: nil
|
||||
}
|
||||
|
||||
upload_after = %Pleroma.Upload{
|
||||
name: "image_with_caption-abstract.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||
tempfile: Path.absname("test/fixtures/image_with_caption-abstract.jpg"),
|
||||
description: "an abstract white pixel"
|
||||
}
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(upload) ==
|
||||
{:ok, :filtered, upload_after}
|
||||
end
|
||||
|
||||
test "otherwise returns nil" do
|
||||
uploads = %Pleroma.Upload{
|
||||
name: "image_with_no_description.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/image_with_no_description.jpg"),
|
||||
tempfile: Path.absname("test/fixtures/image_with_no_description.jpg"),
|
||||
description: nil
|
||||
}
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||
{:ok, :filtered, uploads}
|
||||
end
|
||||
|
||||
test "Return nil when image description from EXIF data exceeds the maximum length" do
|
||||
clear_config([:instance, :description_limit], 5)
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(@uploads) ==
|
||||
{:ok, :filtered, @uploads}
|
||||
end
|
||||
|
||||
test "Ignores content with only whitespace" do
|
||||
uploads = %Pleroma.Upload{
|
||||
name: "non-existant.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path:
|
||||
Path.absname(
|
||||
"test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
|
||||
),
|
||||
tempfile:
|
||||
Path.absname(
|
||||
"test/fixtures/image_with_imagedescription_and_caption-abstract_whitespaces.jpg"
|
||||
),
|
||||
description: nil
|
||||
}
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||
{:ok, :filtered, uploads}
|
||||
end
|
||||
|
||||
test "Return nil when image description from EXIF data can't be read" do
|
||||
uploads = %Pleroma.Upload{
|
||||
name: "non-existant.jpg",
|
||||
content_type: "image/jpeg",
|
||||
path: Path.absname("test/fixtures/non-existant.jpg"),
|
||||
tempfile: Path.absname("test/fixtures/non-existant_tmp.jpg"),
|
||||
description: nil
|
||||
}
|
||||
|
||||
assert Filter.Exiftool.ReadDescription.filter(uploads) ==
|
||||
{:ok, :filtered, uploads}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# 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
|
||||
31
test/pleroma/upload/filter/exiftool_test.exs
Normal file
31
test/pleroma/upload/filter/exiftool_test.exs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# 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
|
||||
44
test/support/exif_helpers.ex
Normal file
44
test/support/exif_helpers.ex
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2026 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Tests.ExifHelpers do
|
||||
@moduledoc """
|
||||
Helpers for upload filter tests that use exiftool as an independent oracle.
|
||||
"""
|
||||
|
||||
@doc "Copies a fixture to a unique temporary file and returns an upload for it."
|
||||
def upload_copy(fixture, content_type, attrs \\ []) do
|
||||
source = Path.absname(Path.join("test/fixtures", fixture))
|
||||
|
||||
tempfile =
|
||||
Path.join(
|
||||
System.tmp_dir!(),
|
||||
"pleroma_exif_#{System.unique_integer([:positive])}#{Path.extname(fixture)}"
|
||||
)
|
||||
|
||||
File.cp!(source, tempfile)
|
||||
ExUnit.Callbacks.on_exit(fn -> File.rm(tempfile) end)
|
||||
|
||||
struct!(
|
||||
%Pleroma.Upload{
|
||||
name: fixture,
|
||||
content_type: content_type,
|
||||
path: source,
|
||||
tempfile: tempfile
|
||||
},
|
||||
attrs
|
||||
)
|
||||
end
|
||||
|
||||
@doc "Returns the GPS tags exiftool finds in a file, as text."
|
||||
def gps_tags(path), do: exiftool!(["-gps:all", path])
|
||||
|
||||
@doc "Returns the value of one tag as exiftool reads it."
|
||||
def tag(path, tag), do: exiftool!(["-s3", "-#{tag}", path])
|
||||
|
||||
defp exiftool!(args) do
|
||||
{output, 0} = System.cmd("exiftool", ["-m" | args])
|
||||
String.trim(output)
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue