Merge remote-tracking branch 'origin/develop' into shigusegubu-new
This commit is contained in:
commit
b36f860a43
151 changed files with 2621 additions and 711 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/advisory-bumps.security
Normal file
1
changelog.d/advisory-bumps.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update ssl_verify_fun to 1.1.9 (EEF-CVE-2026-117666) and phoenix_html to 3.3.5 (EEF-CVE-2026-117645)
|
||||
1
changelog.d/bandit-1.14.1.security
Normal file
1
changelog.d/bandit-1.14.1.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update Bandit to version 1.14.1, which fixes an HTTP/2 request body memory amplification
|
||||
1
changelog.d/c2s-rate-limit.security
Normal file
1
changelog.d/c2s-rate-limit.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Rate limit ActivityPub C2S outbox posts and emoji reactions
|
||||
1
changelog.d/card-skip-quote-link.fix
Normal file
1
changelog.d/card-skip-quote-link.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Link preview cards skip the fallback link of quote posts (`RE: <url>`), which pointed at the quoted post and showed it twice.
|
||||
1
changelog.d/cast-and-validate-cast-opts.fix
Normal file
1
changelog.d/cast-and-validate-cast-opts.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix per-status and per-account rate limits when `Pleroma.Web.ApiSpec.CastAndValidate` `strict` is off (the production default)
|
||||
1
changelog.d/cast-and-validate-many-params.fix
Normal file
1
changelog.d/cast-and-validate-many-params.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Drop all unexpected query params, not just the first one, when `Pleroma.Web.ApiSpec.CastAndValidate` `strict` is off
|
||||
0
changelog.d/ci-cheap-changelog-check.skip
Normal file
0
changelog.d/ci-cheap-changelog-check.skip
Normal file
1
changelog.d/config-db-unknown-module.fix
Normal file
1
changelog.d/config-db-unknown-module.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Do not crash at boot or in the AdminAPI config listing when a ConfigDB setting refers to a module that no longer exists
|
||||
1
changelog.d/context-local-only.fix
Normal file
1
changelog.d/context-local-only.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Show local-only replies in status context to local users who don't follow the replier
|
||||
1
changelog.d/emoji-react-single-tag.fix
Normal file
1
changelog.d/emoji-react-single-tag.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Accept incoming emoji reactions whose tag is a single object instead of an array
|
||||
1
changelog.d/empty-proxy-url.fix
Normal file
1
changelog.d/empty-proxy-url.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
HTTP: treat an empty proxy_url as no proxy; with Hackney 4 it made every outgoing request fail
|
||||
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
|
||||
1
changelog.d/gin-fuzzy-search-limit.fix
Normal file
1
changelog.d/gin-fuzzy-search-limit.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
gin_fuzzy_search_limit is now only set for search queries (configurable under Pleroma.Search.DatabaseSearch), so other GIN index scans such as recipient lookups and Oban job queries are exact again
|
||||
1
changelog.d/gun-ipv6-only-hosts.fix
Normal file
1
changelog.d/gun-ipv6-only-hosts.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Gun adapter: connect to hosts that only have IPv6 (AAAA) addresses
|
||||
1
changelog.d/http-backoff.add
Normal file
1
changelog.d/http-backoff.add
Normal file
|
|
@ -0,0 +1 @@
|
|||
Back off from hosts that answer 429 or 503 to object fetches and WebFinger, until Retry-After or X-RateLimit-Reset (default 5 minutes, max 1 hour)
|
||||
1
changelog.d/jose-1.11.13.security
Normal file
1
changelog.d/jose-1.11.13.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Update jose to version 1.11.13, which fixes JWE, key parsing and signature validation vulnerabilities
|
||||
1
changelog.d/marker-updated-at-format.fix
Normal file
1
changelog.d/marker-updated-at-format.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Mastodon API: Use the Mastodon date format for marker updated_at
|
||||
1
changelog.d/media-proxy-data-urls.fix
Normal file
1
changelog.d/media-proxy-data-urls.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
MediaProxy: Do not proxy data: URLs
|
||||
1
changelog.d/media-proxy-static-redirect.fix
Normal file
1
changelog.d/media-proxy-static-redirect.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Media proxy: serve a preview for static=true on non-GIF files instead of redirecting to the main endpoint host
|
||||
1
changelog.d/move-refetch-target.fix
Normal file
1
changelog.d/move-refetch-target.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Accept a Move when the cached remote target does not list the alias yet, by refetching the target once
|
||||
1
changelog.d/mrf-domain-regex-escape.security
Normal file
1
changelog.d/mrf-domain-regex-escape.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
MRF domain rules now match dots literally, so a rule for `good.example` no longer also matches `goodxexample`, and wildcard rules no longer backtrack on hostile host names
|
||||
1
changelog.d/personal-inbox-addressing.fix
Normal file
1
changelog.d/personal-inbox-addressing.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Accept activities delivered to a personal inbox that do not address the inbox owner (fixes Bridgy Fed follows)
|
||||
1
changelog.d/question-note-fixups.fix
Normal file
1
changelog.d/question-note-fixups.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Apply the Note fixups to incoming Questions, so polls with a single non-array tag or attachment are accepted
|
||||
1
changelog.d/search-remote-fetch.security
Normal file
1
changelog.d/search-remote-fetch.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Status search only fetches remote URLs with resolve=true, on the first page, and for anonymous users only if limit_to_local_content allows it
|
||||
1
changelog.d/shared-inbox.change
Normal file
1
changelog.d/shared-inbox.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Deliver to shared inboxes whenever possible, including followers-only posts and DMs, so each server gets one copy
|
||||
1
changelog.d/signature-freshness.security
Normal file
1
changelog.d/signature-freshness.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Reject incoming HTTP signatures that do not sign the date, are older than 2 hours (or more than 1 hour in the future), or do not sign the digest of a request body, so captured requests cannot be replayed. Set `config :pleroma, :activitypub, signature_policy: :log` to only log these
|
||||
1
changelog.d/stats-peers-from-instances.change
Normal file
1
changelog.d/stats-peers-from-instances.change
Normal file
|
|
@ -0,0 +1 @@
|
|||
Read the peer list from the instances table (hosts of remote actors, without our own host) and update instance stats every 5 minutes; the old peers query scanned all remote users every minute
|
||||
1
changelog.d/status-id-action-rate-limit.fix
Normal file
1
changelog.d/status-id-action-rate-limit.fix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Rate limit favourites and reblogs per status, not across all statuses
|
||||
1
changelog.d/undo-block-outgoing-blocks.security
Normal file
1
changelog.d/undo-block-outgoing-blocks.security
Normal file
|
|
@ -0,0 +1 @@
|
|||
Do not federate the Undo of a block when `outgoing_blocks` is disabled
|
||||
|
|
@ -373,6 +373,9 @@ config :pleroma, :activitypub,
|
|||
note_replies_output_limit: 5,
|
||||
sign_object_fetches: true,
|
||||
authorized_fetch_mode: false,
|
||||
signature_policy: :enforce,
|
||||
signature_max_age: 7200,
|
||||
signature_max_clock_skew: 3600,
|
||||
client_api_enabled: false,
|
||||
anonymize_reporter: false,
|
||||
anonymize_reporter_local_nickname: ""
|
||||
|
|
@ -843,7 +846,7 @@ config :pleroma, :modules, runtime_dir: "instance/modules"
|
|||
config :pleroma, configurable_from_database: false
|
||||
|
||||
config :pleroma, Pleroma.Repo,
|
||||
parameters: [gin_fuzzy_search_limit: "500", jit: "off"],
|
||||
parameters: [jit: "off"],
|
||||
prepare: :unnamed
|
||||
|
||||
config :pleroma, :connections_pool,
|
||||
|
|
@ -948,6 +951,8 @@ config :pleroma, Pleroma.Web.WebFinger, domain: nil, update_nickname_on_user_fet
|
|||
|
||||
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
|
||||
|
||||
config :pleroma, Pleroma.Search.DatabaseSearch, gin_fuzzy_search_limit: 500
|
||||
|
||||
config :pleroma, Pleroma.Search.Meilisearch,
|
||||
url: "http://127.0.0.1:7700/",
|
||||
private_key: nil,
|
||||
|
|
|
|||
|
|
@ -1788,6 +1788,27 @@ config :pleroma, :config_description, [
|
|||
description:
|
||||
"List of IPs (CIDR format accepted) to exempt from HTTP Signatures requirement (for example to allow debugging, you shouldn't otherwise need this)"
|
||||
},
|
||||
%{
|
||||
key: :signature_policy,
|
||||
type: {:dropdown, :atom},
|
||||
description:
|
||||
"What to do with incoming HTTP signatures that do not sign the date (or `(created)`), are outside the allowed time window, or do not sign the digest of a request body. `:enforce` treats them as invalid, `:log` only logs them",
|
||||
suggestions: [:enforce, :log]
|
||||
},
|
||||
%{
|
||||
key: :signature_max_age,
|
||||
type: :integer,
|
||||
description:
|
||||
"How old, in seconds, the signed date of an incoming HTTP signature can be. Default: 7200 (2 hours)",
|
||||
suggestions: [7200]
|
||||
},
|
||||
%{
|
||||
key: :signature_max_clock_skew,
|
||||
type: :integer,
|
||||
description:
|
||||
"How far, in seconds, the signed date of an incoming HTTP signature can be in the future. Default: 3600 (1 hour). A `(created)` time in the future is always rejected",
|
||||
suggestions: [3600]
|
||||
},
|
||||
%{
|
||||
key: :note_replies_output_limit,
|
||||
type: :integer,
|
||||
|
|
@ -3565,6 +3586,21 @@ config :pleroma, :config_description, [
|
|||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Search.DatabaseSearch,
|
||||
type: :group,
|
||||
description: "Built-in database search settings.",
|
||||
children: [
|
||||
%{
|
||||
key: :gin_fuzzy_search_limit,
|
||||
type: :integer,
|
||||
description:
|
||||
"Limits how many matches PostgreSQL considers in GIN full-text search queries (see `gin_fuzzy_search_limit` in the PostgreSQL docs). Only set for search queries. Keeps search fast on large databases, but results become incomplete and change between requests. Set to `nil` for exact results, which is fine on small instances. Default: 500.",
|
||||
suggestions: [500, nil]
|
||||
}
|
||||
]
|
||||
},
|
||||
%{
|
||||
group: :pleroma,
|
||||
key: Pleroma.Search.Meilisearch,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -286,6 +286,9 @@ Notes:
|
|||
* `sign_object_fetches`: Sign object fetches with HTTP signatures
|
||||
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
|
||||
* `authorized_fetch_mode_exceptions`: List of IPs (CIDR format accepted) to exempt from HTTP Signatures requirement (for example to allow debugging, you shouldn't otherwise need this)
|
||||
* `signature_policy`: What to do with incoming HTTP signatures that do not sign the `date` header (or `(created)`), whose signed date is outside the window below, or that do not sign the `digest` header of a request with a body (or whose body Pleroma did not hash). `:enforce` (default) treats them as invalid, `:log` only logs a warning.
|
||||
* `signature_max_age`: How old, in seconds, the signed date of an incoming HTTP signature can be. Default: `7200` (2 hours).
|
||||
* `signature_max_clock_skew`: How far, in seconds, the signed `date` of an incoming HTTP signature can be in the future. Default: `3600` (1 hour). A `(created)` time in the future is always rejected.
|
||||
|
||||
## Pleroma.User
|
||||
|
||||
|
|
@ -702,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.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,14 @@ To use built-in search that has no external dependencies, set the search module
|
|||
|
||||
While it has no external dependencies, it has problems with performance and relevancy.
|
||||
|
||||
With a GIN index (the default), search queries limit how many matches PostgreSQL considers (`gin_fuzzy_search_limit`, see [PostgreSQL's docs](https://www.postgresql.org/docs/current/gin.html#GIN-TIPS)). This keeps searches for common words fast on large databases, but the results are incomplete and can change between requests. On smaller instances you can turn it off for exact results:
|
||||
|
||||
> config :pleroma, Pleroma.Search.DatabaseSearch, gin_fuzzy_search_limit: nil
|
||||
|
||||
Without a limit, searches for common words can take longer than the database timeout on large databases, and then return nothing.
|
||||
|
||||
Earlier versions of Pleroma set this limit for every database connection, which also made other GIN index scans (for example recipient lookups) incomplete. It is now only set for search queries.
|
||||
|
||||
## QdrantSearch
|
||||
|
||||
This uses the vector search engine [Qdrant](https://qdrant.tech) to search the posts in a vector space. This needs a way to generate embeddings and uses the [OpenAI API](https://platform.openai.com/docs/guides/embeddings/what-are-embeddings). This is implemented by several project besides OpenAI itself, including the python-based fastembed-server found in `supplemental/search/fastembed-api`.
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Mix.Pleroma do
|
|||
:oban,
|
||||
:logger_backends
|
||||
]
|
||||
@cachex_children ["object", "user", "scrubber", "web_resp"]
|
||||
@cachex_children ["object", "user", "scrubber", "web_resp", "http_backoff"]
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Pleroma.Config.Holder.save_default()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ defmodule Pleroma.Application do
|
|||
),
|
||||
build_cachex("rel_me", limit: 2500),
|
||||
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5_000),
|
||||
build_cachex("http_backoff", limit: 10_000),
|
||||
build_cachex("translations", default_ttl: :timer.hours(24), limit: 5_000)
|
||||
]
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ defmodule Pleroma.Config.TransferTask do
|
|||
settings =
|
||||
(Repo.all(ConfigDB) ++ deleted_settings)
|
||||
|> Enum.reject(&ConfigDB.static?/1)
|
||||
|> Enum.reject(&(&1.group == :invalid_atom or &1.key == :invalid_atom))
|
||||
|> Enum.map(&merge_with_default/1)
|
||||
|
||||
started_applications = Application.started_applications()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
defmodule Pleroma.EctoType.Config.Atom do
|
||||
use Ecto.Type
|
||||
|
||||
require Logger
|
||||
|
||||
def type, do: :atom
|
||||
|
||||
def cast(key) when is_atom(key) do
|
||||
|
|
@ -17,8 +19,17 @@ defmodule Pleroma.EctoType.Config.Atom do
|
|||
|
||||
def cast(_), do: :error
|
||||
|
||||
# A key or group naming a module that no longer exists (e.g. a removed uploader)
|
||||
# must not crash loading
|
||||
def load(key) do
|
||||
{:ok, Pleroma.ConfigDB.string_to_elixir_types(key)}
|
||||
rescue
|
||||
ArgumentError ->
|
||||
Logger.warning(
|
||||
"ConfigDB: unknown module #{key} in a setting key or group, the setting is not applied"
|
||||
)
|
||||
|
||||
{:ok, :invalid_atom}
|
||||
end
|
||||
|
||||
def dump(key) when is_atom(key), do: {:ok, inspect(key)}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ defmodule Pleroma.Gun.Conn do
|
|||
opts
|
||||
|> Enum.into(%{})
|
||||
|> Map.put_new(:connect_timeout, pool_opts[:connect_timeout] || 5_000)
|
||||
|> Map.put_new(:supervise, false)
|
||||
# Under gun's supervisor, a failed connect does not kill the caller
|
||||
# through the link. Gun still stops when its owner goes down.
|
||||
|> Map.put_new(:supervise, true)
|
||||
|> maybe_add_tls_opts(uri)
|
||||
|
||||
do_open(uri, opts)
|
||||
|
|
@ -117,8 +119,7 @@ defmodule Pleroma.Gun.Conn do
|
|||
host = Pleroma.HTTP.AdapterHelper.parse_host(host)
|
||||
opts = Map.put(opts, :transport, transport(uri.scheme))
|
||||
|
||||
with {:ok, conn} <- Gun.open(host, port, opts),
|
||||
{:ok, protocol} <- await_up(conn, opts[:connect_timeout]) do
|
||||
with {:ok, conn, protocol} <- open_direct(host, port, opts) do
|
||||
{:ok, conn, protocol, nil}
|
||||
else
|
||||
error ->
|
||||
|
|
@ -130,6 +131,40 @@ defmodule Pleroma.Gun.Conn do
|
|||
end
|
||||
end
|
||||
|
||||
defp open_direct(host, port, opts) do
|
||||
result =
|
||||
with {:ok, conn} <- Gun.open(host, port, opts),
|
||||
{:ok, protocol} <- await_up(conn, opts[:connect_timeout]) do
|
||||
{:ok, conn, protocol}
|
||||
end
|
||||
|
||||
case inet6_fallback(result, host, opts) do
|
||||
{:ok, inet6_opts} -> open_direct(host, port, inet6_opts)
|
||||
:error -> result
|
||||
end
|
||||
end
|
||||
|
||||
# Gun's default when :tcp_opts is not set.
|
||||
@default_tcp_opts [send_timeout: 15_000, send_timeout_close: true]
|
||||
|
||||
@doc """
|
||||
Gun resolves hostnames with `inet_tcp` (A records only) unless `:inet6` is
|
||||
in `:tcp_opts`, for both the :tcp and :tls transports. When the A lookup
|
||||
finds nothing, returns the options for one more attempt over IPv6.
|
||||
"""
|
||||
@spec inet6_fallback(term(), Pleroma.HTTP.AdapterHelper.host(), map()) :: {:ok, map()} | :error
|
||||
def inet6_fallback({:error, {:down, {:shutdown, :nxdomain}}}, host, opts) when is_list(host) do
|
||||
tcp_opts = Map.get(opts, :tcp_opts, @default_tcp_opts)
|
||||
|
||||
if :inet6 in tcp_opts do
|
||||
:error
|
||||
else
|
||||
{:ok, Map.put(opts, :tcp_opts, [:inet6 | tcp_opts])}
|
||||
end
|
||||
end
|
||||
|
||||
def inet6_fallback(_result, _host, _opts), do: :error
|
||||
|
||||
defp destination_opts(%URI{host: host, port: port}) do
|
||||
host = Pleroma.HTTP.AdapterHelper.parse_host(host)
|
||||
%{host: host, port: port}
|
||||
|
|
|
|||
|
|
@ -66,14 +66,16 @@ defmodule Pleroma.HTML do
|
|||
end
|
||||
|
||||
@spec extract_first_external_url_from_object(Pleroma.Object.t()) :: String.t() | nil
|
||||
def extract_first_external_url_from_object(%{data: %{"content" => content}})
|
||||
def extract_first_external_url_from_object(%{data: %{"content" => content} = data})
|
||||
when is_binary(content) do
|
||||
# A quote post's fallback link ("RE: <url>", in a .quote-inline element or
|
||||
# bare) points at the quoted post, which is shown already.
|
||||
content
|
||||
|> Floki.parse_fragment!()
|
||||
|> Floki.filter_out(".quote-inline")
|
||||
|> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])")
|
||||
|> Enum.take(1)
|
||||
|> Floki.attribute("href")
|
||||
|> Enum.at(0)
|
||||
|> Enum.find(&(&1 != data["quoteUrl"]))
|
||||
end
|
||||
|
||||
def extract_first_external_url_from_object(_), do: nil
|
||||
|
|
|
|||
|
|
@ -20,8 +20,9 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
|||
|
||||
@callback options(keyword(), URI.t()) :: keyword()
|
||||
|
||||
# AdminFE stores an empty proxy field as "", which means no proxy.
|
||||
@spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil
|
||||
def format_proxy(nil), do: nil
|
||||
def format_proxy(proxy) when proxy in [nil, ""], do: nil
|
||||
|
||||
def format_proxy(proxy_url) do
|
||||
case parse_proxy(proxy_url) do
|
||||
|
|
@ -32,7 +33,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
|||
end
|
||||
|
||||
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
|
||||
def maybe_add_proxy(opts, nil), do: opts
|
||||
def maybe_add_proxy(opts, proxy) when proxy in [nil, ""], do: opts
|
||||
def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
153
lib/pleroma/http/backoff.ex
Normal file
153
lib/pleroma/http/backoff.ex
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.Backoff do
|
||||
@moduledoc """
|
||||
GET wrapper that stops sending requests to a host after it answers
|
||||
429 or 503, until the time given by `Retry-After` or `X-RateLimit-Reset`.
|
||||
|
||||
Based on Akkoma's `Pleroma.HTTP.Backoff`.
|
||||
"""
|
||||
|
||||
alias Pleroma.HTTP
|
||||
|
||||
require Logger
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
@cache :http_backoff_cache
|
||||
|
||||
@default_seconds 5 * 60
|
||||
@max_seconds 60 * 60
|
||||
@max_job_age 2 * 60 * 60
|
||||
|
||||
@spec get(String.t(), list(), keyword()) :: {:ok, Tesla.Env.t()} | {:error, any()}
|
||||
def get(url, headers \\ [], options \\ []) when is_binary(url) do
|
||||
host = url |> URI.parse() |> Map.get(:host) |> downcase()
|
||||
|
||||
case @cachex.get(@cache, host) do
|
||||
{:ok, true} ->
|
||||
{:error, :ratelimit}
|
||||
|
||||
_ ->
|
||||
url
|
||||
|> HTTP.get(headers, options)
|
||||
|> check_backoff(host)
|
||||
end
|
||||
end
|
||||
|
||||
defp downcase(nil), do: nil
|
||||
defp downcase(host), do: String.downcase(host)
|
||||
|
||||
defp check_backoff({:ok, %{status: status, headers: headers} = env}, host)
|
||||
when status in [429, 503] do
|
||||
host = response_host(env, host)
|
||||
seconds = backoff_seconds(headers, DateTime.utc_now())
|
||||
Logger.warning("#{host} answered #{status}, backing off for #{seconds} seconds")
|
||||
@cachex.put(@cache, host, true, ttl: :timer.seconds(seconds))
|
||||
{:error, :ratelimit}
|
||||
end
|
||||
|
||||
defp check_backoff(response, _host), do: response
|
||||
|
||||
# After a redirect, the 429/503 came from the last host, not the requested one.
|
||||
defp response_host(%{url: url}, host) when is_binary(url) do
|
||||
URI.parse(url).host |> downcase() || host
|
||||
end
|
||||
|
||||
defp response_host(_env, host), do: host
|
||||
|
||||
@doc """
|
||||
Turns an Oban job's `{:error, :ratelimit}` result into a snooze for the
|
||||
default backoff, so the job's attempts aren't all spent while the host is
|
||||
backed off. Jobs inserted more than #{@max_job_age} seconds ago get the
|
||||
error back and fail as usual.
|
||||
"""
|
||||
@spec snooze_ratelimited(any(), Oban.Job.t()) :: any()
|
||||
def snooze_ratelimited({:error, :ratelimit} = error, %Oban.Job{inserted_at: inserted_at}) do
|
||||
if is_nil(inserted_at) or DateTime.diff(DateTime.utc_now(), inserted_at) < @max_job_age do
|
||||
{:snooze, @default_seconds}
|
||||
else
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
def snooze_ratelimited(result, _job), do: result
|
||||
|
||||
@doc """
|
||||
Seconds to back off for, from the first valid `Retry-After` or
|
||||
`X-RateLimit-Reset` header. Missing, invalid or past values give the
|
||||
default of #{@default_seconds} seconds; values are capped at #{@max_seconds}.
|
||||
"""
|
||||
@spec backoff_seconds(list(), DateTime.t()) :: pos_integer()
|
||||
def backoff_seconds(headers, now) do
|
||||
[
|
||||
parse_retry_after(header(headers, "retry-after"), now),
|
||||
parse_ratelimit_reset(header(headers, "x-ratelimit-reset"), now)
|
||||
]
|
||||
|> Enum.find(@default_seconds, &is_integer/1)
|
||||
|> min(@max_seconds)
|
||||
end
|
||||
|
||||
defp header(headers, name) do
|
||||
Enum.find_value(headers, fn
|
||||
{key, value} when is_binary(key) and is_binary(value) ->
|
||||
if String.downcase(key) == name, do: String.trim(value)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end)
|
||||
end
|
||||
|
||||
# Retry-After is either delta-seconds or an HTTP-date (RFC 9110, 10.2.3)
|
||||
defp parse_retry_after(nil, _now), do: nil
|
||||
|
||||
defp parse_retry_after(value, now) do
|
||||
case Integer.parse(value) do
|
||||
{seconds, ""} when seconds > 0 -> seconds
|
||||
{_, ""} -> nil
|
||||
_ -> parse_timestamp(value, now)
|
||||
end
|
||||
end
|
||||
|
||||
# Servers send X-RateLimit-Reset as a timestamp (ISO 8601 like Mastodon, or
|
||||
# Unix seconds) or as seconds until the reset.
|
||||
@unix_timestamp_min 1_000_000_000
|
||||
|
||||
defp parse_ratelimit_reset(nil, _now), do: nil
|
||||
|
||||
defp parse_ratelimit_reset(value, now) do
|
||||
case Integer.parse(value) do
|
||||
{timestamp, ""} when timestamp >= @unix_timestamp_min ->
|
||||
timestamp - DateTime.to_unix(now)
|
||||
|
||||
{seconds, ""} ->
|
||||
seconds
|
||||
|
||||
_ ->
|
||||
parse_timestamp(value, now)
|
||||
end
|
||||
|> positive()
|
||||
end
|
||||
|
||||
defp positive(seconds) when is_integer(seconds) and seconds > 0, do: seconds
|
||||
defp positive(_), do: nil
|
||||
|
||||
defp parse_timestamp(nil, _now), do: nil
|
||||
|
||||
defp parse_timestamp(value, now) do
|
||||
with {:ok, %DateTime{} = datetime} <- parse_datetime(value),
|
||||
seconds when seconds > 0 <- DateTime.diff(datetime, now) do
|
||||
seconds
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_datetime(value) do
|
||||
with {:error, _} <- Timex.parse(value, "{RFC1123}"),
|
||||
{:ok, datetime, _offset} <- DateTime.from_iso8601(value) do
|
||||
{:ok, datetime}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -91,6 +91,13 @@ defmodule Pleroma.Instances.Instance do
|
|||
|
||||
def reachable?(url_or_host) when is_binary(url_or_host), do: true
|
||||
|
||||
@doc "Creates a record for the instance unless one exists. Existing records are kept."
|
||||
def ensure_exists(url_or_host) when is_binary(url_or_host) do
|
||||
%Instance{host: host(url_or_host)}
|
||||
|> changeset(%{})
|
||||
|> Repo.insert(on_conflict: :nothing, conflict_target: :host)
|
||||
end
|
||||
|
||||
def set_reachable(url_or_host) when is_binary(url_or_host) do
|
||||
host = host(url_or_host)
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ defmodule Pleroma.Object.Fetcher do
|
|||
|> maybe_date_fetch(date)
|
||||
|> sign_fetch(id, date)
|
||||
|
||||
case HTTP.get(id, headers) do
|
||||
case HTTP.Backoff.get(id, headers) do
|
||||
{:ok, %{body: body, status: code, headers: headers, url: final_url}}
|
||||
when code in 200..299 ->
|
||||
remote_host = if final_url, do: URI.parse(final_url).host, else: nil
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Search.DatabaseSearch do
|
|||
alias Pleroma.Config
|
||||
alias Pleroma.Object.Fetcher
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
|
|
@ -23,22 +24,49 @@ defmodule Pleroma.Search.DatabaseSearch do
|
|||
offset = Keyword.get(options, :offset, 0)
|
||||
author = Keyword.get(options, :author)
|
||||
|
||||
try do
|
||||
Activity
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> restrict_public(user)
|
||||
|> query_with(index_type, search_query)
|
||||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|> maybe_restrict_blocked(user)
|
||||
|> Pagination.fetch_paginated(
|
||||
%{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
|
||||
:offset
|
||||
)
|
||||
|> maybe_fetch(user, search_query)
|
||||
rescue
|
||||
_ -> maybe_fetch([], user, search_query)
|
||||
activities =
|
||||
try do
|
||||
with_fuzzy_limit(fn ->
|
||||
Activity
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Activity.restrict_deactivated_users()
|
||||
|> restrict_public(user)
|
||||
|> query_with(index_type, search_query)
|
||||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|> maybe_restrict_blocked(user)
|
||||
|> Pagination.fetch_paginated(
|
||||
%{"offset" => offset, "limit" => limit, "skip_order" => index_type == :rum},
|
||||
:offset
|
||||
)
|
||||
end)
|
||||
rescue
|
||||
_ -> []
|
||||
end
|
||||
|
||||
# Outside the query transaction, as it may fetch over the network.
|
||||
maybe_fetch(activities, user, search_query, options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Runs `fun` with `gin_fuzzy_search_limit` set, if configured under
|
||||
`Pleroma.Search.DatabaseSearch`. The limit makes GIN scans faster but their
|
||||
results incomplete and random, so it is only set for search queries.
|
||||
"""
|
||||
def with_fuzzy_limit(fun) do
|
||||
case Config.get([__MODULE__, :gin_fuzzy_search_limit]) do
|
||||
limit when is_integer(limit) and limit > 0 ->
|
||||
{:ok, result} =
|
||||
Repo.transaction(fn ->
|
||||
# SET LOCAL takes no parameters; limit is an integer.
|
||||
Repo.query!("SET LOCAL gin_fuzzy_search_limit TO #{limit}")
|
||||
fun.()
|
||||
end)
|
||||
|
||||
result
|
||||
|
||||
_ ->
|
||||
fun.()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -132,7 +160,27 @@ defmodule Pleroma.Search.DatabaseSearch do
|
|||
|
||||
defp restrict_local(q), do: where(q, local: true)
|
||||
|
||||
def maybe_fetch(activities, user, search_query) do
|
||||
@doc """
|
||||
Adds the status a URL query points to, fetching it if needed. As in Mastodon,
|
||||
this only happens with `resolve: true` and on the first page, and for
|
||||
anonymous users only if `[:instance, :limit_to_local_content]` is `false`.
|
||||
"""
|
||||
def maybe_fetch(activities, user, search_query, options) do
|
||||
if fetch_allowed?(user, options),
|
||||
do: fetch(activities, user, search_query),
|
||||
else: activities
|
||||
end
|
||||
|
||||
defp fetch_allowed?(user, options) do
|
||||
options[:resolve] == true and (options[:offset] || 0) == 0 and
|
||||
case Config.get([:instance, :limit_to_local_content], :unauthenticated) do
|
||||
:all -> false
|
||||
:unauthenticated -> match?(%User{}, user)
|
||||
false -> true
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch(activities, user, search_query) do
|
||||
with true <- Regex.match?(~r/https?:/, search_query),
|
||||
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||
|
|
|
|||
|
|
@ -111,11 +111,11 @@ defmodule Pleroma.Search.Meilisearch do
|
|||
|> maybe_restrict_local(user)
|
||||
|> maybe_restrict_author(author)
|
||||
|> maybe_restrict_blocked(user)
|
||||
|> maybe_fetch(user, query)
|
||||
|> maybe_fetch(user, query, options)
|
||||
|> order_by([object: obj], desc: obj.data["published"])
|
||||
|> Pleroma.Repo.all()
|
||||
rescue
|
||||
_ -> maybe_fetch([], user, query)
|
||||
_ -> maybe_fetch([], user, query, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Search.ParadeDB do
|
|||
|
||||
import Pleroma.Search.DatabaseSearch,
|
||||
only: [
|
||||
maybe_fetch: 3,
|
||||
maybe_fetch: 4,
|
||||
maybe_restrict_author: 2,
|
||||
maybe_restrict_blocked: 2,
|
||||
maybe_restrict_local: 2
|
||||
|
|
@ -149,19 +149,19 @@ defmodule Pleroma.Search.ParadeDB do
|
|||
|
||||
case search_ids(query, limit, offset, author) do
|
||||
{:ok, []} ->
|
||||
maybe_fetch([], user, query)
|
||||
maybe_fetch([], user, query, options)
|
||||
|
||||
{:ok, ids} ->
|
||||
ids
|
||||
|> fetch_activities(user, query, author)
|
||||
|> fetch_activities(user, query, author, options)
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("ParadeDB search failed: #{inspect(error)}")
|
||||
maybe_fetch([], user, query)
|
||||
maybe_fetch([], user, query, options)
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_activities(ids, user, query, author) do
|
||||
defp fetch_activities(ids, user, query, author, options) do
|
||||
dumped_ids =
|
||||
ids
|
||||
|> Enum.map(&dump_activity_id/1)
|
||||
|
|
@ -183,7 +183,7 @@ defmodule Pleroma.Search.ParadeDB do
|
|||
)
|
||||
)
|
||||
|> Pleroma.Repo.all()
|
||||
|> maybe_fetch(user, query)
|
||||
|> maybe_fetch(user, query, options)
|
||||
end
|
||||
|
||||
defp maybe_restrict_public(query, %Pleroma.User{}) do
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Search.QdrantSearch do
|
|||
alias __MODULE__.OpenAIClient
|
||||
alias __MODULE__.QdrantClient
|
||||
|
||||
import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 3]
|
||||
import Pleroma.Search.DatabaseSearch, only: [maybe_fetch: 4]
|
||||
|
||||
@impl true
|
||||
def create_index do
|
||||
|
|
@ -131,7 +131,7 @@ defmodule Pleroma.Search.QdrantSearch do
|
|||
|> Activity.restrict_deactivated_users()
|
||||
|> Ecto.Query.order_by([a], fragment("array_position(?, ?)", ^ids, a.id))
|
||||
|> Pleroma.Repo.all()
|
||||
|> maybe_fetch(user, original_query)
|
||||
|> maybe_fetch(user, original_query, options)
|
||||
else
|
||||
_ ->
|
||||
[]
|
||||
|
|
|
|||
|
|
@ -95,6 +95,84 @@ defmodule Pleroma.Signature do
|
|||
Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks the parts of an incoming signature that the cryptographic check does
|
||||
not cover, so that a captured request cannot be replayed later or with a
|
||||
different body.
|
||||
|
||||
The signature must cover `date` or `(created)`, and the signed time must be
|
||||
within the configured window. Requests other than GET and HEAD must also
|
||||
sign `digest`, and DigestPlug must have hashed their body. Based on Akkoma's http_signatures fork and Mastodon's
|
||||
SignedRequest.
|
||||
"""
|
||||
@spec check_signature_policy(Plug.Conn.t(), DateTime.t()) :: :ok | {:error, atom()}
|
||||
def check_signature_policy(%Plug.Conn{} = conn, now \\ DateTime.utc_now()) do
|
||||
signature = HTTPSignatures.signature_for_conn(conn)
|
||||
signed = Map.get(signature, "headers", [])
|
||||
|
||||
cond do
|
||||
"date" not in signed and "(created)" not in signed ->
|
||||
{:error, :date_not_signed}
|
||||
|
||||
conn.method not in ["GET", "HEAD"] and "digest" not in signed ->
|
||||
{:error, :digest_not_signed}
|
||||
|
||||
# The plug puts the digest computed by DigestPlug into the header. With
|
||||
# no computed digest (e.g. multipart), nothing checked the body.
|
||||
conn.method not in ["GET", "HEAD"] and is_nil(conn.assigns[:digest]) ->
|
||||
{:error, :body_not_hashed}
|
||||
|
||||
true ->
|
||||
with :ok <- check_signed_date(conn, signed, now) do
|
||||
check_signed_created(signature, signed, now)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp check_signed_date(conn, signed, now) do
|
||||
if "date" in signed do
|
||||
# Exactly one header: a second one could make this check and the
|
||||
# signature check see different dates.
|
||||
with [date] <- Plug.Conn.get_req_header(conn, "date"),
|
||||
{:ok, %DateTime{} = date} <- Timex.parse(date, "{RFC1123}") do
|
||||
check_time_window(date, now)
|
||||
else
|
||||
_ -> {:error, :invalid_date}
|
||||
end
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp check_signed_created(signature, signed, now) do
|
||||
if "(created)" in signed do
|
||||
with created when is_binary(created) <- signature["created"],
|
||||
{created, ""} <- Integer.parse(created),
|
||||
{:ok, created} <- DateTime.from_unix(created) do
|
||||
check_time_window(created, now)
|
||||
else
|
||||
_ -> {:error, :invalid_date}
|
||||
end
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp check_time_window(signed_at, now) do
|
||||
age = DateTime.diff(now, signed_at)
|
||||
|
||||
cond do
|
||||
age > Pleroma.Config.get([:activitypub, :signature_max_age], 7200) ->
|
||||
{:error, :date_too_old}
|
||||
|
||||
-age > Pleroma.Config.get([:activitypub, :signature_max_clock_skew], 3600) ->
|
||||
{:error, :date_in_future}
|
||||
|
||||
true ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@spec validate_signature(Plug.Conn.t(), String.t()) :: boolean()
|
||||
def validate_signature(%Plug.Conn{} = conn, request_target) do
|
||||
# Newer drafts for HTTP signatures now use @request-target instead of the
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ defmodule Pleroma.Stats do
|
|||
import Ecto.Query
|
||||
|
||||
alias Pleroma.CounterCache
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@interval :timer.seconds(60)
|
||||
@interval :timer.seconds(300)
|
||||
|
||||
def start_link(_) do
|
||||
GenServer.start_link(
|
||||
|
|
@ -64,14 +65,12 @@ defmodule Pleroma.Stats do
|
|||
}
|
||||
}
|
||||
def calculate_stat_data do
|
||||
# Hosts are unique in instances, and remote users add theirs. Scanning
|
||||
# all remote users for their domains was one of the costliest queries.
|
||||
# Favicon lookups for local accounts can add our own host, so skip it.
|
||||
peers =
|
||||
from(
|
||||
u in User,
|
||||
select: fragment("distinct split_part(?, '@', 2)", u.nickname),
|
||||
where: u.local != ^true
|
||||
)
|
||||
from(i in Instance, where: i.host != ^local_host(), select: i.host)
|
||||
|> Repo.all()
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
domain_count = Enum.count(peers)
|
||||
|
||||
|
|
@ -97,6 +96,9 @@ defmodule Pleroma.Stats do
|
|||
}
|
||||
end
|
||||
|
||||
# Read from the config: this also runs before the endpoint has started.
|
||||
defp local_host, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
|
||||
|
||||
@spec get_status_visibility_count(String.t() | nil) :: map()
|
||||
def get_status_visibility_count(instance \\ nil) do
|
||||
if is_nil(instance) do
|
||||
|
|
|
|||
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
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Hashtag
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Maps
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
|
|
@ -465,21 +466,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
def fetch_activities_for_context_query(context, opts) do
|
||||
public = [Constants.as_public()]
|
||||
user = opts[:user]
|
||||
|
||||
recipients =
|
||||
if opts[:user],
|
||||
do: [opts[:user].ap_id | User.following(opts[:user])] ++ public,
|
||||
else: public
|
||||
cond do
|
||||
user && user.local ->
|
||||
[Constants.as_public(), as_local_public(), user.ap_id | User.following(user)]
|
||||
|
||||
user ->
|
||||
[Constants.as_public(), user.ap_id | User.following(user)]
|
||||
|
||||
true ->
|
||||
[Constants.as_public()]
|
||||
end
|
||||
|
||||
from(activity in Activity)
|
||||
|> maybe_preload_objects(opts)
|
||||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_set_thread_muted_field(opts)
|
||||
|> restrict_unauthenticated(opts[:user])
|
||||
|> restrict_unauthenticated(user)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_blockers_visibility(opts)
|
||||
|> restrict_recipients(recipients, opts[:user])
|
||||
|> restrict_recipients(recipients, user)
|
||||
|> restrict_filtered(opts)
|
||||
|> where(
|
||||
[activity],
|
||||
|
|
@ -1948,6 +1956,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
Logger.info("Rejected user #{ap_id}: #{inspect(reason)}")
|
||||
{:error, e}
|
||||
|
||||
{:error, :ratelimit = e} ->
|
||||
Logger.debug("Backing off from fetching user #{ap_id}")
|
||||
{:error, e}
|
||||
|
||||
{:error, e} ->
|
||||
Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||
{:error, e}
|
||||
|
|
@ -2057,6 +2069,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
def make_user_from_ap_id(ap_id, additional \\ []) do
|
||||
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
|
||||
enqueue_pin_fetches(data)
|
||||
# Keeps the instances table complete, so it can list our peers.
|
||||
Instance.ensure_exists(data.ap_id)
|
||||
|
||||
# the fetched actor's id differs from ap_id when it was fetched from a display URL
|
||||
user = User.get_cached_by_ap_id(data.ap_id)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
|
||||
alias Pleroma.Web.Plugs.FederatingPlug
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.Plugs.RateLimiter
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -66,6 +67,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
when action in [:followers, :following]
|
||||
)
|
||||
|
||||
# Shares the per-user bucket with Mastodon API status actions and emoji reactions.
|
||||
plug(RateLimiter, [name: :statuses_actions] when action == :update_outbox)
|
||||
|
||||
plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
|
||||
|
||||
plug(
|
||||
|
|
@ -341,7 +345,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
{:sender_exists, User.get_or_fetch_by_ap_id(params["actor"])},
|
||||
{:recipient_active, true} <- {:recipient_active, recipient.is_active},
|
||||
{:sender_active, true} <- {:sender_active, actor.is_active},
|
||||
true <- Utils.recipient_in_message(recipient, actor, params),
|
||||
params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
|
||||
Federator.incoming_ap_doc(params)
|
||||
json(conn, "ok")
|
||||
|
|
@ -386,18 +389,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
end
|
||||
|
||||
def inbox(%{assigns: %{valid_signature: false}} = conn, params) do
|
||||
if unsupported_http_message_signature?(conn) do
|
||||
reject_unsupported_http_message_signature(conn)
|
||||
else
|
||||
Federator.incoming_failed_signature_ap_doc(%{
|
||||
method: conn.method,
|
||||
req_headers: conn.req_headers,
|
||||
request_path: conn.request_path,
|
||||
params: params,
|
||||
query_string: conn.query_string
|
||||
})
|
||||
cond do
|
||||
unsupported_http_message_signature?(conn) ->
|
||||
reject_unsupported_http_message_signature(conn)
|
||||
|
||||
json(conn, "ok")
|
||||
reason = conn.assigns[:signature_error] ->
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> json("error, invalid HTTP signature (#{reason})")
|
||||
|
||||
true ->
|
||||
Federator.incoming_failed_signature_ap_doc(%{
|
||||
method: conn.method,
|
||||
req_headers: conn.req_headers,
|
||||
request_path: conn.request_path,
|
||||
params: params,
|
||||
query_string: conn.query_string
|
||||
})
|
||||
|
||||
json(conn, "ok")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -148,14 +148,13 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
@spec subdomains_regex([String.t()]) :: [Regex.t()]
|
||||
def subdomains_regex(domains) when is_list(domains) do
|
||||
for domain <- domains do
|
||||
try do
|
||||
target = String.replace(domain, "*.", "(.*\\.)*")
|
||||
~r<^#{target}$>i
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("MRF: Invalid subdomain Regex: #{domain}")
|
||||
reraise e, __STACKTRACE__
|
||||
end
|
||||
{prefix, rest} =
|
||||
case domain do
|
||||
"*." <> rest -> {"(.*\\.)?", rest}
|
||||
_ -> {"", domain}
|
||||
end
|
||||
|
||||
~r<^#{prefix}#{Regex.escape(rest)}$>i
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -78,10 +78,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
|||
|> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object)
|
||||
undone_object = Activity.get_by_ap_id(object["object"])
|
||||
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
|
||||
# if we're undoing a block, and do not permit federating that:
|
||||
do_not_federate =
|
||||
Keyword.get(meta, :do_not_federate) ||
|
||||
(Map.get(undone_object.data, "type") == "Block" && !outgoing_blocks)
|
||||
|
||||
meta =
|
||||
meta
|
||||
|> Keyword.put(:object_data, undone_object.data)
|
||||
|> Keyword.put(:do_not_federate, do_not_federate)
|
||||
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
|||
|
||||
defp fix_misskey_content(object), do: object
|
||||
|
||||
defp fix(data) do
|
||||
def fix(data) do
|
||||
data
|
||||
|> CommonFixes.fix_actor()
|
||||
|> CommonFixes.fix_object_defaults()
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
|||
|> CommonFixes.fix_actor()
|
||||
|> CommonFixes.fix_activity_addressing()
|
||||
|
||||
data = Map.put_new(data, "tag", [])
|
||||
data = fix_tag(data)
|
||||
|
||||
case Object.normalize(data["object"]) do
|
||||
%Object{} = object ->
|
||||
|
|
@ -70,6 +70,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
|||
end
|
||||
end
|
||||
|
||||
# JSON-LD compaction turns a single-element array into a bare object
|
||||
defp fix_tag(%{"tag" => %{} = tag} = data), do: Map.put(data, "tag", [tag])
|
||||
defp fix_tag(data), do: Map.put_new(data, "tag", [])
|
||||
|
||||
defp fix_emoji_qualification(%{"content" => emoji} = data) do
|
||||
new_emoji = Pleroma.Emoji.fully_qualify_emoji(emoji)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
|
|
@ -62,11 +61,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
|||
|
||||
defp fix(data) do
|
||||
data
|
||||
|> CommonFixes.fix_actor()
|
||||
|> CommonFixes.fix_object_defaults()
|
||||
|> CommonFixes.fix_quote_url()
|
||||
|> CommonFixes.fix_likes()
|
||||
|> Transmogrifier.fix_emoji()
|
||||
|> ArticleNotePageValidator.fix()
|
||||
|> fix_closed()
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -281,36 +281,18 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
defp maybe_use_sharedinbox(%User{shared_inbox: shared_inbox}), do: shared_inbox
|
||||
|
||||
@doc """
|
||||
Determine a user inbox to use based on heuristics. These heuristics
|
||||
are based on an approximation of the ``sharedInbox`` rules in the
|
||||
[ActivityPub specification][ap-sharedinbox].
|
||||
Determines the inbox to deliver an activity to.
|
||||
|
||||
Please do not edit this function (or its children) without reading
|
||||
the spec, as editing the code is likely to introduce some breakage
|
||||
without some familiarity.
|
||||
|
||||
[ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
|
||||
Like Mastodon, GoToSocial and Akkoma, this always prefers the shared inbox,
|
||||
which saves duplicate deliveries to the same server. Only activities with
|
||||
`bcc` or `bto` go to personal inboxes, because the receiver rebuilds that
|
||||
addressing from the inbox the activity arrived at.
|
||||
"""
|
||||
def determine_inbox(
|
||||
%Activity{data: activity_data},
|
||||
%User{inbox: inbox} = user
|
||||
) do
|
||||
to = activity_data["to"] || []
|
||||
cc = activity_data["cc"] || []
|
||||
type = activity_data["type"]
|
||||
|
||||
cond do
|
||||
type == "Delete" ->
|
||||
maybe_use_sharedinbox(user)
|
||||
|
||||
Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
|
||||
maybe_use_sharedinbox(user)
|
||||
|
||||
length(to) + length(cc) > 1 ->
|
||||
maybe_use_sharedinbox(user)
|
||||
|
||||
true ->
|
||||
inbox
|
||||
def determine_inbox(%Activity{data: activity_data}, %User{inbox: inbox} = user) do
|
||||
if Enum.empty?(activity_data["bcc"] || []) and Enum.empty?(activity_data["bto"] || []) do
|
||||
maybe_use_sharedinbox(user)
|
||||
else
|
||||
inbox
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -799,7 +799,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
) do
|
||||
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
|
||||
true <- origin_actor in target_user.also_known_as do
|
||||
{:ok, %User{} = target_user} <- refetch_unless_aliased(target_user, origin_actor) do
|
||||
ActivityPub.move(origin_user, target_user, false)
|
||||
else
|
||||
_e -> :error
|
||||
|
|
@ -1204,4 +1204,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def maybe_fix_user_url(data), do: data
|
||||
|
||||
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
|
||||
|
||||
# A Move usually follows right after the alias was added, which the cached
|
||||
# remote target may not have yet.
|
||||
defp refetch_unless_aliased(%User{} = target, origin_ap_id, refetch? \\ true) do
|
||||
cond do
|
||||
origin_ap_id in target.also_known_as ->
|
||||
{:ok, target}
|
||||
|
||||
refetch? and not target.local ->
|
||||
with {:ok, %User{} = target} <- User.fetch_by_ap_id(target.ap_id),
|
||||
do: refetch_unless_aliased(target, origin_ap_id, false)
|
||||
|
||||
true ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -78,22 +78,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
[params["to"], params["cc"], params["bto"], params["bcc"]]
|
||||
|> Enum.any?(&label_in_collection?(label, &1))
|
||||
|
||||
@spec unaddressed_message?(map()) :: boolean()
|
||||
def unaddressed_message?(params),
|
||||
do:
|
||||
[params["to"], params["cc"], params["bto"], params["bcc"]]
|
||||
|> Enum.all?(fn
|
||||
nil -> true
|
||||
[] -> true
|
||||
_ -> false
|
||||
end)
|
||||
|
||||
@spec recipient_in_message(User.t(), User.t(), map()) :: boolean()
|
||||
def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
|
||||
do:
|
||||
label_in_message?(ap_id, params) || unaddressed_message?(params) ||
|
||||
User.following?(recipient, actor)
|
||||
|
||||
defp extract_list(target) when is_binary(target), do: [target]
|
||||
defp extract_list(lst) when is_list(lst), do: lst
|
||||
defp extract_list(_), do: []
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
|||
|
||||
require Logger
|
||||
|
||||
# How often to drop offending query params and retry in permissive mode
|
||||
@max_retries 20
|
||||
|
||||
@impl Plug
|
||||
def init(opts) do
|
||||
opts
|
||||
|
|
@ -114,34 +117,49 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
|
|||
false = _strict,
|
||||
cast_opts
|
||||
) do
|
||||
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
|
||||
{:ok, conn} ->
|
||||
{:ok, conn}
|
||||
permissive_cast_and_validate(spec, operation, conn, content_type, cast_opts, @max_retries)
|
||||
end
|
||||
|
||||
# Remove unexpected query params and cast/validate again
|
||||
{:error, errors} ->
|
||||
query_params =
|
||||
Enum.reduce(errors, conn.query_params, fn
|
||||
%{reason: :unexpected_field, name: name, path: [name]}, params ->
|
||||
Map.delete(params, name)
|
||||
# OpenApiSpex reports one offending param at a time, so drop it and cast/validate again
|
||||
defp permissive_cast_and_validate(
|
||||
spec,
|
||||
operation,
|
||||
%Conn{} = conn,
|
||||
content_type,
|
||||
cast_opts,
|
||||
retries
|
||||
) do
|
||||
with {:error, errors} = error <-
|
||||
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) do
|
||||
query_params = drop_invalid_query_params(errors, conn.query_params)
|
||||
|
||||
# Filter out empty params
|
||||
%{reason: :invalid_type, path: [name_atom], value: ""}, params ->
|
||||
Map.delete(params, to_string(name_atom))
|
||||
|
||||
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
|
||||
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
|
||||
update_in(params, path, &List.delete(&1, value))
|
||||
|
||||
_, params ->
|
||||
params
|
||||
end)
|
||||
|
||||
conn = %Conn{conn | query_params: query_params}
|
||||
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
|
||||
if retries > 0 and query_params != conn.query_params do
|
||||
conn = %{conn | query_params: query_params}
|
||||
permissive_cast_and_validate(spec, operation, conn, content_type, cast_opts, retries - 1)
|
||||
else
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp drop_invalid_query_params(errors, query_params) do
|
||||
Enum.reduce(errors, query_params, fn
|
||||
%{reason: :unexpected_field, name: name, path: [name]}, params ->
|
||||
Map.delete(params, name)
|
||||
|
||||
# Filter out empty params
|
||||
%{reason: :invalid_type, path: [name_atom], value: ""}, params ->
|
||||
Map.delete(params, to_string(name_atom))
|
||||
|
||||
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
|
||||
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
|
||||
update_in(params, path, &List.delete(&1, value))
|
||||
|
||||
_, params ->
|
||||
params
|
||||
end)
|
||||
end
|
||||
|
||||
defp list_items_to_string(list) do
|
||||
Enum.map(list, fn
|
||||
i when is_atom(i) -> to_string(i)
|
||||
|
|
|
|||
|
|
@ -92,13 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
|
|||
|
||||
plug(
|
||||
RateLimiter,
|
||||
[name: :status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: [:id]]
|
||||
[name: :status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]]
|
||||
when action in ~w(reblog unreblog)a
|
||||
)
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
[name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: [:id]]
|
||||
[name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
|
||||
when action in ~w(favourite unfavourite)a
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.MarkerView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
def render("markers.json", %{markers: markers}) do
|
||||
Map.new(markers, fn m ->
|
||||
{m.timeline,
|
||||
%{
|
||||
last_read_id: m.last_read_id,
|
||||
version: m.lock_version,
|
||||
updated_at: NaiveDateTime.to_iso8601(m.updated_at),
|
||||
updated_at: Utils.to_masto_date(m.updated_at),
|
||||
pleroma: %{
|
||||
unread_count: m.unread_count
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
def url("/" <> _ = url), do: url
|
||||
|
||||
def url(url) do
|
||||
if enabled?() and url_proxiable?(url) do
|
||||
if enabled?() and not data_url?(url) and url_proxiable?(url) do
|
||||
encode_url(url)
|
||||
else
|
||||
url
|
||||
|
|
@ -61,7 +61,7 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
def preview_url("//" <> _ = url, pparams), do: preview_url("https:" <> url, pparams)
|
||||
|
||||
def preview_url(url, preview_params) do
|
||||
if preview_enabled?() do
|
||||
if preview_enabled?() and not data_url?(url) do
|
||||
encode_preview_url(url, preview_params)
|
||||
else
|
||||
url(url)
|
||||
|
|
@ -74,6 +74,8 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
# non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
|
||||
def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled])
|
||||
|
||||
defp data_url?(url), do: url |> String.slice(0, 5) |> String.downcase() == "data:"
|
||||
|
||||
def local?(url), do: String.starts_with?(url, Endpoint.url())
|
||||
|
||||
def whitelisted?(url) do
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Helpers.MediaHelper
|
||||
alias Pleroma.Helpers.UriHelper
|
||||
alias Pleroma.ReverseProxy
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Plug.Conn
|
||||
|
|
@ -67,9 +66,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
static and content_type == "image/gif" ->
|
||||
handle_jpeg_preview(conn, media_proxy_url)
|
||||
|
||||
static ->
|
||||
drop_static_param_and_redirect(conn)
|
||||
|
||||
content_type == "image/gif" ->
|
||||
conn
|
||||
|> put_status(301)
|
||||
|
|
@ -166,15 +162,6 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
end
|
||||
end
|
||||
|
||||
defp drop_static_param_and_redirect(conn) do
|
||||
uri_without_static_param =
|
||||
conn
|
||||
|> current_url()
|
||||
|> UriHelper.modify_uri_params(%{}, ["static"])
|
||||
|
||||
redirect(conn, external: uri_without_static_param)
|
||||
end
|
||||
|
||||
defp fallback_on_preview_error(conn, media_proxy_url) do
|
||||
redirect(conn, external: media_proxy_url)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
|||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||
alias Pleroma.Web.Plugs.RateLimiter
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete])
|
||||
|
|
@ -22,6 +23,14 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
|||
when action == :index
|
||||
)
|
||||
|
||||
plug(
|
||||
RateLimiter,
|
||||
[name: :status_id_action, bucket_name: "status_id_action:emoji_react", params: [:id]]
|
||||
when action in [:create, :delete]
|
||||
)
|
||||
|
||||
plug(RateLimiter, [name: :statuses_actions] when action in [:create, :delete])
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.EmojiReactionOperation
|
||||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
|
|
|||
|
|
@ -45,20 +45,52 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
|||
|
||||
defp maybe_assign_valid_signature(conn) do
|
||||
if has_signature_header?(conn) do
|
||||
# we replace the digest header with the one we computed in DigestPlug
|
||||
conn =
|
||||
case conn do
|
||||
%{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
|
||||
conn -> conn
|
||||
end
|
||||
conn = put_computed_digest(conn)
|
||||
|
||||
assign(conn, :valid_signature, Signature.validate_signature(conn))
|
||||
case check_signature_policy(conn) do
|
||||
:ok ->
|
||||
assign(conn, :valid_signature, Signature.validate_signature(conn))
|
||||
|
||||
{:error, reason} ->
|
||||
reject_signature(conn, reason)
|
||||
end
|
||||
else
|
||||
Logger.debug("No signature header!")
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
# we replace the digest header with the one we computed in DigestPlug
|
||||
defp put_computed_digest(%{assigns: %{digest: digest}} = conn),
|
||||
do: put_req_header(conn, "digest", digest)
|
||||
|
||||
defp put_computed_digest(conn), do: conn
|
||||
|
||||
defp check_signature_policy(conn) do
|
||||
with {:error, reason} <- Signature.check_signature_policy(conn) do
|
||||
if Pleroma.Config.get([:activitypub, :signature_policy], :enforce) == :log do
|
||||
Logger.warning(
|
||||
"HTTP signature policy violation (#{reason}) for #{conn.method} #{conn.request_path}, allowed by config"
|
||||
)
|
||||
|
||||
:ok
|
||||
else
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The request is treated as badly signed. The reason is kept so the inbox
|
||||
# can refuse it instead of queueing a failed-signature retry, which only
|
||||
# checks the cryptographic signature.
|
||||
defp reject_signature(conn, reason) do
|
||||
Logger.debug("Rejecting HTTP signature: #{reason}")
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|> assign(:signature_error, reason)
|
||||
end
|
||||
|
||||
defp maybe_assign_actor_id(%{assigns: %{valid_signature: true}} = conn) do
|
||||
adapter = Application.get_env(:http_signatures, :adapter)
|
||||
|
||||
|
|
|
|||
|
|
@ -158,7 +158,11 @@ defmodule Pleroma.Web.WebFinger do
|
|||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
def find_lrdd_template(domain) do
|
||||
@cachex.fetch!(:host_meta_cache, domain, fn _ ->
|
||||
{:commit, fetch_lrdd_template(domain)}
|
||||
case fetch_lrdd_template(domain) do
|
||||
# retry once the backoff expires instead of caching the miss
|
||||
{:error, :ratelimit} = error -> {:ignore, error}
|
||||
result -> {:commit, result}
|
||||
end
|
||||
end)
|
||||
rescue
|
||||
e -> {:error, "Cachex error: #{inspect(e)}"}
|
||||
|
|
@ -168,9 +172,13 @@ defmodule Pleroma.Web.WebFinger do
|
|||
# WebFinger is restricted to HTTPS - https://tools.ietf.org/html/rfc7033#section-9.1
|
||||
meta_url = "https://#{domain}/.well-known/host-meta"
|
||||
|
||||
with {:ok, %{status: status, body: body}} when status in 200..299 <- HTTP.get(meta_url) do
|
||||
with {:ok, %{status: status, body: body}} when status in 200..299 <-
|
||||
HTTP.Backoff.get(meta_url) do
|
||||
get_template_from_xml(body)
|
||||
else
|
||||
{:error, :ratelimit} = error ->
|
||||
error
|
||||
|
||||
error ->
|
||||
Logger.warning("Can't find LRDD template in #{inspect(meta_url)}: #{inspect(error)}")
|
||||
{:error, :lrdd_not_found}
|
||||
|
|
@ -210,7 +218,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
with address when is_binary(address) <- get_address_from_domain(domain, account),
|
||||
{:ok, %{status: status, body: body, headers: headers}} when status in 200..299 <-
|
||||
HTTP.get(
|
||||
HTTP.Backoff.get(
|
||||
address,
|
||||
[{"accept", "application/xrd+xml,application/jrd+json"}]
|
||||
) do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.ReceiverWorker do
|
||||
alias Pleroma.HTTP.Backoff
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Workers.SignatureRetryWorker
|
||||
|
|
@ -14,7 +15,9 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
if signature_retry_job?(args) do
|
||||
perform_signature_retry(job)
|
||||
else
|
||||
perform_incoming(params)
|
||||
params
|
||||
|> perform_incoming()
|
||||
|> Backoff.snooze_ratelimited(job)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.SignatureRetryWorker do
|
||||
alias Pleroma.HTTP.Backoff
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.User
|
||||
|
|
@ -16,16 +17,18 @@ defmodule Pleroma.Workers.SignatureRetryWorker do
|
|||
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{
|
||||
args: %{
|
||||
"op" => "incoming_failed_signature_ap_doc",
|
||||
"method" => method,
|
||||
"params" => params,
|
||||
"req_headers" => req_headers,
|
||||
"request_path" => request_path,
|
||||
"query_string" => query_string
|
||||
}
|
||||
})
|
||||
def perform(
|
||||
%Job{
|
||||
args: %{
|
||||
"op" => "incoming_failed_signature_ap_doc",
|
||||
"method" => method,
|
||||
"params" => params,
|
||||
"req_headers" => req_headers,
|
||||
"request_path" => request_path,
|
||||
"query_string" => query_string
|
||||
}
|
||||
} = job
|
||||
)
|
||||
when is_binary(method) and is_map(params) and is_list(req_headers) and
|
||||
is_binary(request_path) and is_binary(query_string) do
|
||||
case normalize_req_headers(req_headers) do
|
||||
|
|
@ -58,7 +61,10 @@ defmodule Pleroma.Workers.SignatureRetryWorker do
|
|||
|
||||
{:ok, res}
|
||||
else
|
||||
e -> process_errors(e, retry_log_context(params, request_path, signature_actor_result))
|
||||
e ->
|
||||
e
|
||||
|> process_errors(retry_log_context(params, request_path, signature_actor_result))
|
||||
|> Backoff.snooze_ratelimited(job)
|
||||
end
|
||||
|
||||
e ->
|
||||
|
|
|
|||
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},
|
||||
|
|
|
|||
9
mix.lock
9
mix.lock
|
|
@ -1,7 +1,7 @@
|
|||
%{
|
||||
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
|
||||
"argon2_elixir": {:hex, :argon2_elixir, "4.1.3", "4f28318286f89453364d7fbb53e03d4563fd7ed2438a60237eba5e426e97785f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "7c295b8d8e0eaf6f43641698f962526cdf87c6feb7d14bd21e599271b510608c"},
|
||||
"bandit": {:hex, :bandit, "1.14.0", "555af34f126edb98d8b6b95bbeba4d5f4c3af21e37535d791f32db2365c532fb", [:mix], [{:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.6", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "2d07630e4bf77b55240881386f7629bcc2f87426a299860b2d31c4ad77a48746"},
|
||||
"bandit": {:hex, :bandit, "1.14.1", "5fb450e2360faf03d9b9fb45cfe52e14197f62d376c34781219bec0f9bc7ca06", [:mix], [{:hpax, "~> 1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.6", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "87c7ee5e08fb5f22d4e072e681e1101266a21432889b08ef8d0cb0c1450f8bba"},
|
||||
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
|
||||
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
|
||||
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
|
||||
|
|
@ -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"},
|
||||
|
|
@ -68,7 +69,7 @@
|
|||
"inet_cidr": {:hex, :inet_cidr, "1.0.9", "e0ef72a2942529da78c8e4147d53f2ef5f6f5293335c3637b0fdf83c012cc816", [:mix], [], "hexpm", "172da15ff7cf635b1feaf14f5818be28c811b37cc5fb7c5f7c01058c1c1066cc"},
|
||||
"jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
|
||||
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
|
||||
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
|
||||
"jose": {:hex, :jose, "1.11.13", "94abcd8bf98eb9f007272918bd3f5ffb613e8d250567de233e6c2b2456319005", [:mix, :rebar3], [], "hexpm", "15d8bd0f01ab29d1771e8da1f60f9c98725b23a17f7312cdd40e8e2757bd98c7"},
|
||||
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
|
||||
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
|
||||
"logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"},
|
||||
|
|
@ -105,7 +106,7 @@
|
|||
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"},
|
||||
"phoenix": {:hex, :phoenix, "1.8.11", "9bb8f0c4e9f0b9eaf7f34cf8cb0bd25bd8599763a5587d64918a9ebb04027823", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 2.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "44f028f4129e5a29487e868f84903373e3d032da151ad0c789c3849f464e7351"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.5", "c4ef322acd15a574a8b1a08eff0ee0a85e73096b53ce1403b6563709f15e1cea", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "26ec3208eef407f31b748cadd044045c6fd485fbff168e35963d2f9dfff28d4b"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
|
||||
"phoenix_html": {:hex, :phoenix_html, "3.3.5", "a2ce9bfc938bd7e23f10de438d19550074d7fdba3ea8d54095142f52da288fec", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "9966451c70e3080124b5a6b9b30f8a7927983528d890fd457f9eee4074dfa167"},
|
||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"},
|
||||
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
|
||||
"phoenix_live_view": {:hex, :phoenix_live_view, "1.2.9", "d35ddac2fab4480e6bdbf712ff104fd9e969a2bbe81b578ee80f066b371f56db", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2f9528c3d7046edabbb30a91710ca33988f8d8bc20a964a1fc48b32134572afa"},
|
||||
|
|
@ -135,7 +136,7 @@
|
|||
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
|
||||
"remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"},
|
||||
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.9", "4ff292dc16e728fde1235cf5fa6bcd3ca01864004526f71a4a70935509d1a7f1", [:make, :mix, :rebar3], [], "hexpm", "19f24ad715ef6255d530b2f0db8b2d2191ae435b6527234e943f523e84ed0895"},
|
||||
"statistex": {:hex, :statistex, "1.1.0", "7fec1eb2f580a0d2c1a05ed27396a084ab064a40cfc84246dbfb0c72a5c761e5", [:mix], [], "hexpm", "f5950ea26ad43246ba2cce54324ac394a4e7408fdcf98b8e230f503a0cba9cf5"},
|
||||
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
|
||||
"swoosh": {:hex, :swoosh, "1.28.0", "30d9f3519a150128e90d97f9878a8a5cd8325ed03d618d1c626201496fc20baf", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, ">= 1.9.0 and < 5.0.0", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, ">= 6.0.0 and < 8.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bb5c0b7c988beb53786254a61580597fe1017a652039061ee2b4c28b5097b10e"},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# 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.BackfillInstancesFromUsers do
|
||||
use Ecto.Migration
|
||||
|
||||
# Remote users now create an instances row; add the hosts of existing ones,
|
||||
# so the peer list can come from the instances table.
|
||||
def up do
|
||||
Pleroma.Repo.query!(
|
||||
"""
|
||||
INSERT INTO instances (host, inserted_at, updated_at)
|
||||
SELECT DISTINCT host, now(), now()
|
||||
FROM (
|
||||
-- Skips userinfo; IPv6 literals give NULL and are left out.
|
||||
SELECT substring(ap_id from '^[A-Za-z][A-Za-z0-9+.-]*://(?:[^/?#@]*@)?([^/:?#\\[]+)') AS host
|
||||
FROM users
|
||||
WHERE local = false AND ap_id IS NOT NULL
|
||||
) AS hosts
|
||||
WHERE host IS NOT NULL
|
||||
ON CONFLICT (host) DO NOTHING
|
||||
""",
|
||||
[],
|
||||
timeout: :infinity
|
||||
)
|
||||
end
|
||||
|
||||
def down, do: :ok
|
||||
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.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue