logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: 05cb931e4d5e312477cc6c4cf80ab10180b84fd1
parent e798be90accbc93e9c7102a51ffd2575c9e617ec
Author: marcin mikołajczak <git@mkljczk.pl>
Date:   Thu, 22 Feb 2024 12:03:06 +0100

Merge remote-tracking branch 'origin/develop' into post-languages

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>

Diffstat:

M.gitignore5+++--
MCHANGELOG.md5+++++
Achangelog.d/atom-leak.skip0
Achangelog.d/bandit.change1+
Achangelog.d/bugfix-ccworks.fix2++
Achangelog.d/config-stat-symlink.fix1+
Achangelog.d/content-length.fix1+
Achangelog.d/dialyzer4.skip0
Achangelog.d/gun-logs.skip0
Achangelog.d/gun_pool.fix1+
Achangelog.d/memleak.fix1+
Achangelog.d/mergeback-2.6.2.skip0
Achangelog.d/notifications-index.fix1+
Achangelog.d/oauth-nickname.skip2++
Achangelog.d/rich_media.fix1+
Achangelog.d/rich_media_tests.skip0
Achangelog.d/tesla.deps1+
Achangelog.d/websocket-refactor.change1+
Mconfig/config.exs9+--------
Mconfig/dev.exs3+--
Mlib/mix/tasks/pleroma/emoji.ex2+-
Dlib/phoenix/transports/web_socket/raw.ex93-------------------------------------------------------------------------------
Mlib/pleroma/activity/html.ex2+-
Mlib/pleroma/caching.ex3+++
Mlib/pleroma/config/deprecation_warnings.ex12++++++------
Mlib/pleroma/config/release_runtime_provider.ex2+-
Mlib/pleroma/emoji/pack.ex2+-
Mlib/pleroma/filter.ex3---
Mlib/pleroma/gun/connection_pool/worker_supervisor.ex4+++-
Mlib/pleroma/html.ex31+++++++++++--------------------
Mlib/pleroma/http/request_builder.ex4++--
Mlib/pleroma/maps.ex15++++++++++++++-
Mlib/pleroma/mfa.ex2+-
Mlib/pleroma/mfa/totp.ex3++-
Mlib/pleroma/notification.ex2+-
Mlib/pleroma/pagination.ex9+++++----
Mlib/pleroma/password/pbkdf2.ex2+-
Mlib/pleroma/reverse_proxy.ex2+-
Mlib/pleroma/telemetry/logger.ex4++--
Mlib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex2++
Mlib/pleroma/web/activity_pub/object_validators/common_fixes.ex3+++
Mlib/pleroma/web/activity_pub/transmogrifier.ex1+
Mlib/pleroma/web/controller_helper.ex2+-
Mlib/pleroma/web/embed_controller.ex2--
Mlib/pleroma/web/endpoint.ex44+++++++++++++++++++++++++++++++++-----------
Mlib/pleroma/web/mastodon_api/controllers/search_controller.ex2+-
Mlib/pleroma/web/mastodon_api/websocket_handler.ex256++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mlib/pleroma/web/o_auth/o_auth_controller.ex7+------
Mlib/pleroma/web/o_status/o_status_controller.ex1-
Mlib/pleroma/web/pleroma_api/controllers/mascot_controller.ex2+-
Mlib/pleroma/web/plugs/rate_limiter/supervisor.ex2+-
Mlib/pleroma/web/rich_media/helpers.ex62++++++++++++++++++++------------------------------------------
Mlib/pleroma/web/rich_media/parser.ex151++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mlib/pleroma/web/static_fe/static_fe_controller.ex1-
Mlib/pleroma/web/templates/o_auth/o_auth/show.html.eex2+-
Mlib/pleroma/workers/background_worker.ex2+-
Mmix.exs5+++--
Mmix.lock6++++--
Mpriv/gettext/ja/LC_MESSAGES/errors.po90++++++++++++++++++++++++++++++++++++++++----------------------------------------
Atest/fixtures/ccworld-ap-bridge_note.json1+
Atest/fixtures/rich_media/google.html12++++++++++++
Mtest/fixtures/rich_media/oembed.html2+-
Atest/fixtures/rich_media/yahoo.html12++++++++++++
Mtest/pleroma/integration/mastodon_websocket_test.exs11-----------
Atest/pleroma/maps_test.exs22++++++++++++++++++++++
Mtest/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs26++++++++++++++++++++++++++
Mtest/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs11+++++++++++
Mtest/pleroma/web/mastodon_api/controllers/status_controller_test.exs8+-------
Mtest/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs1+
Mtest/pleroma/web/rich_media/helpers_test.exs44+++++++++++++++++++++++++++++++++++---------
Mtest/pleroma/web/rich_media/parser_test.exs105++++++++++++++-----------------------------------------------------------------
Mtest/support/cachex_proxy.ex6++++++
Mtest/support/http_request_mock.ex70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtest/support/null_cache.ex6++++++
74 files changed, 635 insertions(+), 577 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -57,5 +57,6 @@ pleroma.iml .tool-versions # Editor temp files -/*~ -/*# +*~ +*# +*.swp diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2.6.2 + +### Security +- MRF StealEmojiPolicy: Sanitize shortcodes (thanks to Hazel K for the report + ## 2.6.1 ### Changed - - Document maximum supported version of Erlang & Elixir diff --git a/changelog.d/atom-leak.skip b/changelog.d/atom-leak.skip diff --git a/changelog.d/bandit.change b/changelog.d/bandit.change @@ -0,0 +1 @@ +Support Bandit as an alternative to Cowboy for the HTTP server. diff --git a/changelog.d/bugfix-ccworks.fix b/changelog.d/bugfix-ccworks.fix @@ -0,0 +1 @@ +Fix federation with Convergence AP Bridge +\ No newline at end of file diff --git a/changelog.d/config-stat-symlink.fix b/changelog.d/config-stat-symlink.fix @@ -0,0 +1 @@ +- Config: Check the permissions of the linked file instead of the symlink diff --git a/changelog.d/content-length.fix b/changelog.d/content-length.fix @@ -0,0 +1 @@ +MediaProxy was setting the content-length header which is not permitted by RFC9112§6.2 when we are chunking the reply as it conflicts with the existence of the transfer-encoding header. diff --git a/changelog.d/dialyzer4.skip b/changelog.d/dialyzer4.skip diff --git a/changelog.d/gun-logs.skip b/changelog.d/gun-logs.skip diff --git a/changelog.d/gun_pool.fix b/changelog.d/gun_pool.fix @@ -0,0 +1 @@ +Fix logic error in Gun connection pooling which prevented retries even when the worker was launched with retry = true diff --git a/changelog.d/memleak.fix b/changelog.d/memleak.fix @@ -0,0 +1 @@ +Fix a memory leak caused by Websocket connections that would not enter a state where a full garbage collection run could be triggered. diff --git a/changelog.d/mergeback-2.6.2.skip b/changelog.d/mergeback-2.6.2.skip diff --git a/changelog.d/notifications-index.fix b/changelog.d/notifications-index.fix @@ -0,0 +1 @@ +Fix notifications query which was not using the index properly diff --git a/changelog.d/oauth-nickname.skip b/changelog.d/oauth-nickname.skip @@ -0,0 +1 @@ +Use User.full_nickname/1 in oauth html template +\ No newline at end of file diff --git a/changelog.d/rich_media.fix b/changelog.d/rich_media.fix @@ -0,0 +1 @@ +Rich Media Preview cache eviction when the activity is updated. diff --git a/changelog.d/rich_media_tests.skip b/changelog.d/rich_media_tests.skip diff --git a/changelog.d/tesla.deps b/changelog.d/tesla.deps @@ -0,0 +1 @@ +Update Tesla HTTP client middleware to 1.8.0 diff --git a/changelog.d/websocket-refactor.change b/changelog.d/websocket-refactor.change @@ -0,0 +1 @@ +Refactor the Mastodon /api/v1/streaming websocket handler to use Phoenix.Socket.Transport diff --git a/config/config.exs b/config/config.exs @@ -114,14 +114,7 @@ config :pleroma, :uri_schemes, config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], http: [ - ip: {127, 0, 0, 1}, - dispatch: [ - {:_, - [ - {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} - ]} - ] + ip: {127, 0, 0, 1} ], protocol: "https", secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl", diff --git a/config/dev.exs b/config/dev.exs @@ -8,8 +8,7 @@ import Config # with brunch.io to recompile .js and .css sources. config :pleroma, Pleroma.Web.Endpoint, http: [ - port: 4000, - protocol_options: [max_request_line_length: 8192, max_header_value_length: 8192] + port: 4000 ], protocol: "http", debug_errors: true, diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex @@ -111,7 +111,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do {:ok, _} = :zip.unzip(binary_archive, - cwd: pack_path, + cwd: String.to_charlist(pack_path), file_list: files_to_unzip ) diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex @@ -1,93 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Phoenix.Transports.WebSocket.Raw do - import Plug.Conn, - only: [ - fetch_query_params: 1, - send_resp: 3 - ] - - alias Phoenix.Socket.Transport - - def default_config do - [ - timeout: 60_000, - transport_log: false, - cowboy: Phoenix.Endpoint.CowboyWebSocket - ] - end - - def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do - {_, opts} = handler.__transport__(transport) - - conn = - conn - |> fetch_query_params - |> Transport.transport_log(opts[:transport_log]) - |> Transport.check_origin(handler, endpoint, opts) - - case conn do - %{halted: false} = conn -> - case handler.connect(%{ - endpoint: endpoint, - transport: transport, - options: [serializer: nil], - params: conn.params - }) do - {:ok, socket} -> - {:ok, conn, {__MODULE__, {socket, opts}}} - - :error -> - send_resp(conn, :forbidden, "") - {:error, conn} - end - - _ -> - {:error, conn} - end - end - - def init(conn, _) do - send_resp(conn, :bad_request, "") - {:error, conn} - end - - def ws_init({socket, config}) do - Process.flag(:trap_exit, true) - {:ok, %{socket: socket}, config[:timeout]} - end - - def ws_handle(op, data, state) do - state.socket.handler - |> apply(:handle, [op, data, state]) - |> case do - {op, data} -> - {:reply, {op, data}, state} - - {op, data, state} -> - {:reply, {op, data}, state} - - %{} = state -> - {:ok, state} - - _ -> - {:ok, state} - end - end - - def ws_info({_, _} = tuple, state) do - {:reply, tuple, state} - end - - def ws_info(_tuple, state), do: {:ok, state} - - def ws_close(state) do - ws_handle(:closed, :normal, state) - end - - def ws_terminate(reason, state) do - ws_handle(:closed, reason, state) - end -end diff --git a/lib/pleroma/activity/html.ex b/lib/pleroma/activity/html.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Activity.HTML do end end - defp add_cache_key_for(activity_id, additional_key) do + def add_cache_key_for(activity_id, additional_key) do current = get_cache_keys_for(activity_id) unless additional_key in current do diff --git a/lib/pleroma/caching.ex b/lib/pleroma/caching.ex @@ -8,10 +8,13 @@ defmodule Pleroma.Caching do @callback put(Cachex.cache(), any(), any(), Keyword.t()) :: {Cachex.status(), boolean()} @callback put(Cachex.cache(), any(), any()) :: {Cachex.status(), boolean()} @callback fetch!(Cachex.cache(), any(), function() | nil) :: any() + @callback fetch(Cachex.cache(), any(), function() | nil) :: + {atom(), any()} | {atom(), any(), any()} # @callback del(Cachex.cache(), any(), Keyword.t()) :: {Cachex.status(), boolean()} @callback del(Cachex.cache(), any()) :: {Cachex.status(), boolean()} @callback stream!(Cachex.cache(), any()) :: Enumerable.t() @callback expire_at(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()} + @callback expire(Cachex.cache(), binary(), number()) :: {Cachex.status(), boolean()} @callback exists?(Cachex.cache(), any()) :: {Cachex.status(), boolean()} @callback execute!(Cachex.cache(), function()) :: any() @callback get_and_update(Cachex.cache(), any(), function()) :: diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex @@ -256,7 +256,7 @@ defmodule Pleroma.Config.DeprecationWarnings do move_namespace_and_warn(@mrf_config_map, warning_preface) end - @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | nil + @spec move_namespace_and_warn([config_map()], String.t()) :: :ok | :error def move_namespace_and_warn(config_map, warning_preface) do warning = Enum.reduce(config_map, "", fn @@ -279,7 +279,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_media_proxy_whitelist_config() :: :ok | nil + @spec check_media_proxy_whitelist_config() :: :ok | :error def check_media_proxy_whitelist_config do whitelist = Config.get([:media_proxy, :whitelist]) @@ -340,7 +340,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_activity_expiration_config() :: :ok | nil + @spec check_activity_expiration_config() :: :ok | :error def check_activity_expiration_config do warning_preface = """ !!!DEPRECATION WARNING!!! @@ -356,7 +356,7 @@ defmodule Pleroma.Config.DeprecationWarnings do ) end - @spec check_remote_ip_plug_name() :: :ok | nil + @spec check_remote_ip_plug_name() :: :ok | :error def check_remote_ip_plug_name do warning_preface = """ !!!DEPRECATION WARNING!!! @@ -372,7 +372,7 @@ defmodule Pleroma.Config.DeprecationWarnings do ) end - @spec check_uploaders_s3_public_endpoint() :: :ok | nil + @spec check_uploaders_s3_public_endpoint() :: :ok | :error def check_uploaders_s3_public_endpoint do s3_config = Pleroma.Config.get([Pleroma.Uploaders.S3]) @@ -393,7 +393,7 @@ defmodule Pleroma.Config.DeprecationWarnings do end end - @spec check_old_chat_shoutbox() :: :ok | nil + @spec check_old_chat_shoutbox() :: :ok | :error def check_old_chat_shoutbox do instance_config = Pleroma.Config.get([:instance]) chat_config = Pleroma.Config.get([:chat]) || [] diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex @@ -21,7 +21,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do with_runtime_config = if File.exists?(config_path) do # <https://git.pleroma.social/pleroma/pleroma/-/issues/3135> - %File.Stat{mode: mode} = File.lstat!(config_path) + %File.Stat{mode: mode} = File.stat!(config_path) if Bitwise.band(mode, 0o007) > 0 do raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}" diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex @@ -100,7 +100,7 @@ defmodule Pleroma.Emoji.Pack do {:ok, _emoji_files} = :zip.unzip( to_charlist(file.path), - [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] + [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, String.to_charlist(tmp_dir)}] ) {_, updated_pack} = diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex @@ -216,9 +216,6 @@ defmodule Pleroma.Filter do :re -> ~r/\b#{phrases}\b/i - - _ -> - nil end end diff --git a/lib/pleroma/gun/connection_pool/worker_supervisor.ex b/lib/pleroma/gun/connection_pool/worker_supervisor.ex @@ -21,7 +21,9 @@ defmodule Pleroma.Gun.ConnectionPool.WorkerSupervisor do def start_worker(opts, retry \\ false) do case DynamicSupervisor.start_child(__MODULE__, {Pleroma.Gun.ConnectionPool.Worker, opts}) do {:error, :max_children} -> - if Enum.any?([retry, free_pool()], &match?(&1, :error)) do + funs = [fn -> !retry end, fn -> match?(:error, free_pool()) end] + + if Enum.any?(funs, fn fun -> fun.() end) do :telemetry.execute([:pleroma, :connection_pool, :provision_failure], %{opts: opts}) {:error, :pool_full} else diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex @@ -6,8 +6,6 @@ defmodule Pleroma.HTML do # Scrubbers are compiled on boot so they can be configured in OTP releases # @on_load :compile_scrubbers - @cachex Pleroma.Config.get([:cachex, :provider], Cachex) - def compile_scrubbers do dir = Path.join(:code.priv_dir(:pleroma), "scrubbers") @@ -67,27 +65,20 @@ defmodule Pleroma.HTML do end end - def extract_first_external_url_from_object(%{data: %{"content" => content}} = object) + @spec extract_first_external_url_from_object(Pleroma.Object.t()) :: + {:ok, String.t()} | {:error, :no_content} + def extract_first_external_url_from_object(%{data: %{"content" => content}}) when is_binary(content) do - unless object.data["fake"] do - key = "URL|#{object.id}" + url = + content + |> Floki.parse_fragment!() + |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") + |> Enum.take(1) + |> Floki.attribute("href") + |> Enum.at(0) - @cachex.fetch!(:scrubber_cache, key, fn _key -> - {:commit, {:ok, extract_first_external_url(content)}} - end) - else - {:ok, extract_first_external_url(content)} - end + {:ok, url} end def extract_first_external_url_from_object(_), do: {:error, :no_content} - - def extract_first_external_url(content) do - content - |> Floki.parse_fragment!() - |> Floki.find("a:not(.mention,.hashtag,.attachment,[rel~=\"tag\"])") - |> Enum.take(1) - |> Floki.attribute("href") - |> Enum.at(0) - end end diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex @@ -54,12 +54,12 @@ defmodule Pleroma.HTTP.RequestBuilder do @doc """ Add optional parameters to the request """ - @spec add_param(Request.t(), atom(), atom(), any()) :: Request.t() + @spec add_param(Request.t(), atom(), atom() | String.t(), any()) :: Request.t() def add_param(request, :query, :query, values), do: %{request | query: values} def add_param(request, :body, :body, value), do: %{request | body: value} - def add_param(request, :body, key, value) do + def add_param(request, :body, key, value) when is_binary(key) do request |> Map.put(:body, Multipart.new()) |> Map.update!( diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Maps do @@ -18,4 +18,17 @@ defmodule Pleroma.Maps do rescue _ -> data end + + def filter_empty_values(data) do + # TODO: Change to Map.filter in Elixir 1.13+ + data + |> Enum.filter(fn + {_k, nil} -> false + {_k, ""} -> false + {_k, []} -> false + {_k, %{} = v} -> Map.keys(v) != [] + {_k, _v} -> true + end) + |> Map.new() + end end diff --git a/lib/pleroma/mfa.ex b/lib/pleroma/mfa.ex @@ -77,7 +77,7 @@ defmodule Pleroma.MFA do {:ok, codes} else {:error, msg} -> - %{error: msg} + {:error, msg} end end diff --git a/lib/pleroma/mfa/totp.ex b/lib/pleroma/mfa/totp.ex @@ -14,6 +14,7 @@ defmodule Pleroma.MFA.TOTP do @doc """ https://github.com/google/google-authenticator/wiki/Key-Uri-Format """ + @spec provisioning_uri(String.t(), String.t(), list()) :: String.t() def provisioning_uri(secret, label, opts \\ []) do query = %{ @@ -27,7 +28,7 @@ defmodule Pleroma.MFA.TOTP do |> URI.encode_query() %URI{scheme: "otpauth", host: "totp", path: "/" <> label, query: query} - |> URI.to_string() + |> to_string() end defp default_period, do: Config.get(@config_ns ++ [:period]) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Notification do where: q.seen == true, select: type(q.id, :string), limit: 1, - order_by: [desc: :id] + order_by: fragment("? desc nulls last", q.id) ) end diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex @@ -61,15 +61,16 @@ defmodule Pleroma.Pagination do |> Repo.all() end - @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] - def paginate(query, options, method \\ :keyset, table_binding \\ nil) - - def paginate(list, options, _method, _table_binding) when is_list(list) do + @spec paginate_list(list(), keyword()) :: list() + def paginate_list(list, options) do offset = options[:offset] || 0 limit = options[:limit] || 0 Enum.slice(list, offset, limit) end + @spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()] + def paginate(query, options, method \\ :keyset, table_binding \\ nil) + def paginate(query, options, :keyset, table_binding) do query |> restrict(:min_id, options, table_binding) diff --git a/lib/pleroma/password/pbkdf2.ex b/lib/pleroma/password/pbkdf2.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Password.Pbkdf2 do iterations = String.to_integer(iterations) - digest = String.to_atom(digest) + digest = String.to_existing_atom(digest) binary_hash = KeyGenerator.generate(password, salt, digest: digest, iterations: iterations, length: 64) diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex @@ -8,7 +8,7 @@ defmodule Pleroma.ReverseProxy do ~w(if-unmodified-since if-none-match) ++ @range_headers @resp_cache_headers ~w(etag date last-modified) @keep_resp_headers @resp_cache_headers ++ - ~w(content-length content-type content-disposition content-encoding) ++ + ~w(content-type content-disposition content-encoding) ++ ~w(content-range accept-ranges vary) @default_cache_control_header "public, max-age=1209600" @valid_resp_codes [200, 206, 304] diff --git a/lib/pleroma/telemetry/logger.ex b/lib/pleroma/telemetry/logger.ex @@ -59,7 +59,7 @@ defmodule Pleroma.Telemetry.Logger do _, _ ) do - Logger.error(fn -> + Logger.debug(fn -> "Connection pool had to refuse opening a connection to #{key} due to connection limit exhaustion" end) end @@ -81,7 +81,7 @@ defmodule Pleroma.Telemetry.Logger do %{key: key, protocol: :http}, _ ) do - Logger.info(fn -> + Logger.debug(fn -> "Pool worker for #{key}: #{length(clients)} clients are using an HTTP1 connection at the same time, head-of-line blocking might occur." end) end diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -36,6 +36,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do extension = if extension == "", do: ".png", else: extension + shortcode = Path.basename(shortcode) file_path = Path.join(emoji_dir_path, shortcode <> extension) case File.write(file_path, response.body) do @@ -78,6 +79,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do new_emojis = foreign_emojis |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) + |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\"]) end) |> Enum.filter(fn {shortcode, _url} -> reject_emoji? = [:mrf_steal_emoji, :rejected_shortcodes] diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do alias Pleroma.EctoType.ActivityPub.ObjectValidators + alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.User @@ -29,6 +30,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do end def fix_object_defaults(data) do + data = Maps.filter_empty_values(data) + context = Utils.maybe_create_context( data["context"] || data["conversation"] || data["inReplyTo"] || data["id"] diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -337,6 +337,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def fix_tag(object), do: object + # prefer content over contentMap def fix_content_map(%{"content" => content} = object) when not_empty_string(content), do: object # content map usually only has one language so this will do for now. diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.ControllerHelper do |> json(json) end - @spec fetch_integer_param(map(), String.t(), integer() | nil) :: integer() | nil + @spec fetch_integer_param(map(), String.t() | atom(), integer() | nil) :: integer() | nil def fetch_integer_param(params, name, default \\ nil) do params |> Map.get(name, default) diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex @@ -11,8 +11,6 @@ defmodule Pleroma.Web.EmbedController do alias Pleroma.Web.ActivityPub.Visibility - plug(:put_layout, :embed) - def show(conn, %{"id" => id}) do with %Activity{local: true} = activity <- Activity.get_by_id_with_object(id), diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex @@ -9,6 +9,16 @@ defmodule Pleroma.Web.Endpoint do alias Pleroma.Config + socket("/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, + longpoll: false, + websocket: [ + path: "/", + compress: false, + error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []}, + fullsweep_after: 20 + ] + ) + socket("/socket", Pleroma.Web.UserSocket, websocket: [ path: "/websocket", @@ -18,7 +28,8 @@ defmodule Pleroma.Web.Endpoint do ], timeout: 60_000, transport_log: false, - compress: false + compress: false, + fullsweep_after: 20 ], longpoll: false ) @@ -32,7 +43,8 @@ defmodule Pleroma.Web.Endpoint do plug(Pleroma.Web.Plugs.HTTPSecurityPlug) plug(Pleroma.Web.Plugs.UploadedMedia) - @static_cache_control "public, no-cache" + @static_cache_control "public, max-age=1209600" + @static_cache_disabled "public, no-cache" # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well @@ -43,22 +55,32 @@ defmodule Pleroma.Web.Endpoint do from: :pleroma, only: ["emoji", "images"], gzip: true, - cache_control_for_etags: "public, max-age=1209600", + cache_control_for_etags: @static_cache_control, headers: %{ - "cache-control" => "public, max-age=1209600" + "cache-control" => @static_cache_control } ) plug(Pleroma.Web.Plugs.InstanceStatic, at: "/", gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled + } + ) + + plug(Pleroma.Web.Plugs.FrontendStatic, + at: "/", + frontend_type: :primary, + only: ["index.html"], + gzip: true, + cache_control_for_etags: @static_cache_disabled, + headers: %{ + "cache-control" => @static_cache_disabled } ) - # Careful! No `only` restriction here, as we don't know what frontends contain. plug(Pleroma.Web.Plugs.FrontendStatic, at: "/", frontend_type: :primary, @@ -75,9 +97,9 @@ defmodule Pleroma.Web.Endpoint do at: "/pleroma/admin", frontend_type: :admin, gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) @@ -92,9 +114,9 @@ defmodule Pleroma.Web.Endpoint do only: Pleroma.Constants.static_only_files(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, - cache_control_for_etags: @static_cache_control, + cache_control_for_etags: @static_cache_disabled, headers: %{ - "cache-control" => @static_cache_control + "cache-control" => @static_cache_disabled } ) diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex @@ -156,7 +156,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do tags end - Pleroma.Pagination.paginate(tags, options) + Pleroma.Pagination.paginate_list(tags, options) end defp add_joined_tag(tags) do diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -11,28 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do alias Pleroma.Web.Streamer alias Pleroma.Web.StreamerView - @behaviour :cowboy_websocket + @behaviour Phoenix.Socket.Transport # Client ping period. @tick :timer.seconds(30) - # Cowboy timeout period. - @timeout :timer.seconds(60) - # Hibernate every X messages - @hibernate_every 100 - - def init(%{qs: qs} = req, state) do - with params <- Enum.into(:cow_qs.parse_qs(qs), %{}), - sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil), - access_token <- Map.get(params, "access_token"), - {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket), - {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do - req = - if sec_websocket do - :cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req) - else - req - end + @impl Phoenix.Socket.Transport + def child_spec(_opts), do: :ignore + + # This only prepares the connection and is not in the process yet + @impl Phoenix.Socket.Transport + def connect(%{params: params} = transport_info) do + with access_token <- Map.get(params, "access_token"), + {:ok, user, oauth_token} <- authenticate_request(access_token), + {:ok, topic} <- + Streamer.get_topic(params["stream"], user, oauth_token, params) do topics = if topic do [topic] @@ -40,41 +33,40 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do [] end - {:cowboy_websocket, req, - %{user: user, topics: topics, oauth_token: oauth_token, count: 0, timer: nil}, - %{idle_timeout: @timeout}} + state = %{ + user: user, + topics: topics, + oauth_token: oauth_token, + count: 0, + timer: nil + } + + {:ok, state} else {:error, :bad_topic} -> - Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") - req = :cowboy_req.reply(404, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} bad topic #{inspect(transport_info)}") + + {:error, :bad_topic} {:error, :unauthorized} -> - Logger.debug("#{__MODULE__} authentication error: #{inspect(req)}") - req = :cowboy_req.reply(401, req) - {:ok, req, state} + Logger.debug("#{__MODULE__} authentication error: #{inspect(transport_info)}") + {:error, :unauthorized} end end - def websocket_init(state) do - Logger.debug( - "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics}" - ) - + # All subscriptions/links and messages cannot be created + # until the processed is launched with init/1 + @impl Phoenix.Socket.Transport + def init(state) do Enum.each(state.topics, fn topic -> Streamer.add_socket(topic, state.oauth_token) end) - {:ok, %{state | timer: timer()}} - end - # Client's Pong frame. - def websocket_handle(:pong, state) do - if state.timer, do: Process.cancel_timer(state.timer) - {:ok, %{state | timer: timer()}} - end + Process.send_after(self(), :ping, @tick) - # We only receive pings for now - def websocket_handle(:ping, state), do: {:ok, state} + {:ok, state} + end - def websocket_handle({:text, text}, state) do + @impl Phoenix.Socket.Transport + def handle_in({text, [opcode: :text]}, state) do with {:ok, %{} = event} <- Jason.decode(text) do handle_client_event(event, state) else @@ -84,50 +76,47 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - def websocket_handle(frame, state) do + def handle_in(frame, state) do Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") {:ok, state} end - def websocket_info({:render_with_user, view, template, item, topic}, state) do + @impl Phoenix.Socket.Transport + def handle_info({:render_with_user, view, template, item, topic}, state) do user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) unless Streamer.filtered_by_user?(user, item) do - websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user}) + message = view.render(template, item, user, topic) + {:push, {:text, message}, %{state | user: user}} else {:ok, state} end end - def websocket_info({:text, message}, state) do - # If the websocket processed X messages, force an hibernate/GC. - # We don't hibernate at every message to balance CPU usage/latency with RAM usage. - if state.count > @hibernate_every do - {:reply, {:text, message}, %{state | count: 0}, :hibernate} - else - {:reply, {:text, message}, %{state | count: state.count + 1}} - end + def handle_info({:text, text}, state) do + {:push, {:text, text}, state} end - # Ping tick. We don't re-queue a timer there, it is instead queued when :pong is received. - # As we hibernate there, reset the count to 0. - # If the client misses :pong, Cowboy will automatically timeout the connection after - # `@idle_timeout`. - def websocket_info(:tick, state) do - {:reply, :ping, %{state | timer: nil, count: 0}, :hibernate} + def handle_info(:ping, state) do + Process.send_after(self(), :ping, @tick) + + {:push, {:ping, ""}, state} end - def websocket_info(:close, state) do - {:stop, state} + def handle_info(:close, state) do + {:stop, {:closed, 'connection closed by server'}, state} end - # State can be `[]` only in case we terminate before switching to websocket, - # we already log errors for these cases in `init/1`, so just do nothing here - def terminate(_reason, _req, []), do: :ok + def handle_info(msg, state) do + Logger.debug("#{__MODULE__} received info: #{inspect(msg)}") - def terminate(reason, _req, state) do + {:ok, state} + end + + @impl Phoenix.Socket.Transport + def terminate(reason, state) do Logger.debug( - "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)}" + "#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topics #{state.topics || "?"}: #{inspect(reason)})" ) Enum.each(state.topics, fn topic -> Streamer.remove_socket(topic) end) @@ -135,16 +124,13 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end # Public streams without authentication. - defp authenticate_request(nil, nil) do + defp authenticate_request(nil) do {:ok, nil, nil} end # Authenticated streams. - defp authenticate_request(access_token, sec_websocket) do - token = access_token || sec_websocket - - with true <- is_bitstring(token), - oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token), + defp authenticate_request(access_token) do + with oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), user = %User{} <- User.get_cached_by_id(user_id) do {:ok, user, oauth_token} else @@ -152,36 +138,32 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do end end - defp timer do - Process.send_after(self(), :tick, @tick) - end - defp handle_client_event(%{"type" => "subscribe", "stream" => _topic} = params, state) do with {_, {:ok, topic}} <- {:topic, Streamer.get_topic(params["stream"], state.user, state.oauth_token, params)}, {_, false} <- {:subscribed, topic in state.topics} do Streamer.add_socket(topic, state.oauth_token) - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"})} - ], %{state | topics: [topic | state.topics]}} + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: [topic | state.topics]}} else {:subscribed, true} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"})} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{type: "subscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} {:topic, {:error, error}} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "subscribe", - result: "error", - error: error - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "subscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} end end @@ -191,26 +173,26 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do {_, true} <- {:subscribed, topic in state.topics} do Streamer.remove_socket(topic) - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"})} - ], %{state | topics: List.delete(state.topics, topic)}} + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "success"}) + + {:reply, :ok, {:text, message}, %{state | topics: List.delete(state.topics, topic)}} else {:subscribed, false} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"})} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{type: "unsubscribe", result: "ignored"}) + + {:reply, :error, {:text, message}, state} {:topic, {:error, error}} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "unsubscribe", - result: "error", - error: error - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "unsubscribe", + result: "error", + error: error + }) + + {:reply, :error, {:text, message}, state} end end @@ -219,39 +201,47 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do state ) do with {:auth, nil, nil} <- {:auth, state.user, state.oauth_token}, - {:ok, user, oauth_token} <- authenticate_request(access_token, nil) do - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "success" - })} - ], %{state | user: user, oauth_token: oauth_token}} + {:ok, user, oauth_token} <- authenticate_request(access_token) do + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "success" + }) + + {:reply, :ok, {:text, message}, %{state | user: user, oauth_token: oauth_token}} else {:auth, _, _} -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "error", - error: :already_authenticated - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :already_authenticated + }) + + {:reply, :error, {:text, message}, state} _ -> - {[ - {:text, - StreamerView.render("pleroma_respond.json", %{ - type: "pleroma:authenticate", - result: "error", - error: :unauthorized - })} - ], state} + message = + StreamerView.render("pleroma_respond.json", %{ + type: "pleroma:authenticate", + result: "error", + error: :unauthorized + }) + + {:reply, :error, {:text, message}, state} end end defp handle_client_event(params, state) do Logger.error("#{__MODULE__} received unknown event: #{inspect(params)}") - {[], state} + {:ok, state} + end + + def handle_error(conn, :unauthorized) do + Plug.Conn.send_resp(conn, 401, "Unauthorized") + end + + def handle_error(conn, _reason) do + Plug.Conn.send_resp(conn, 404, "Not Found") end end diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -610,13 +610,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - @spec validate_scopes(App.t(), map() | list()) :: + @spec validate_scopes(App.t(), list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(%App{} = app, params) when is_map(params) do - requested_scopes = Scopes.fetch_scopes(params, app.scopes) - validate_scopes(app, requested_scopes) - end - defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do Scopes.validate(requested_scopes, app.scopes) end diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex @@ -112,7 +112,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do conn - |> put_layout(:metadata_player) |> put_resp_header("x-frame-options", "ALLOW") |> put_resp_header( "content-security-policy", diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do _ ) do with {:content_type, "image" <> _} <- {:content_type, file.content_type}, - {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do + {_, {:ok, object}} <- {:upload, ActivityPub.upload(file, actor: User.ap_id(user))} do attachment = render_attachment(object) {:ok, _user} = User.mascot_update(user, attachment) diff --git a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor ] - opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor] + opts = [strategy: :one_for_one] Supervisor.init(children, opts) end end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) @options [ @@ -16,51 +18,10 @@ defmodule Pleroma.Web.RichMedia.Helpers do recv_timeout: 2_000 ] - @spec validate_page_url(URI.t() | binary()) :: :ok | :error - defp validate_page_url(page_url) when is_binary(page_url) do - validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) - - page_url - |> Linkify.Parser.url?(validate_tld: validate_tld) - |> parse_uri(page_url) - end - - defp validate_page_url(%URI{host: host, scheme: "https", authority: authority}) - when is_binary(authority) do - cond do - host in @config_impl.get([:rich_media, :ignore_hosts], []) -> - :error - - get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> - :error - - true -> - :ok - end - end - - defp validate_page_url(_), do: :error - - defp parse_uri(true, url) do - url - |> URI.parse() - |> validate_page_url - end - - defp parse_uri(_, _), do: :error - - defp get_tld(host) do - host - |> String.split(".") - |> Enum.reverse() - |> hd - end - def fetch_data_for_object(object) do with true <- @config_impl.get([:rich_media, :enabled]), {:ok, page_url} <- HTML.extract_first_external_url_from_object(object), - :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do %{page_url: page_url, rich_media: rich_media} else @@ -71,7 +32,24 @@ defmodule Pleroma.Web.RichMedia.Helpers do def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do with true <- @config_impl.get([:rich_media, :enabled]), %Object{} = object <- Object.normalize(activity, fetch: false) do - fetch_data_for_object(object) + if object.data["fake"] do + fetch_data_for_object(object) + else + key = "URL|#{activity.id}" + + @cachex.fetch!(:scrubber_cache, key, fn _ -> + result = fetch_data_for_object(object) + + cond do + match?(%{page_url: _, rich_media: _}, result) -> + Activity.HTML.add_cache_key_for(activity.id, key) + {:commit, result} + + true -> + {:ignore, %{}} + end + end) + end else _ -> %{} end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Web.RichMedia.Parser do require Logger @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config) defp parsers do Pleroma.Config.get([:rich_media, :parsers]) @@ -13,70 +14,66 @@ defmodule Pleroma.Web.RichMedia.Parser do def parse(nil), do: {:error, "No URL provided"} - if Pleroma.Config.get(:env) == :test do - @spec parse(String.t()) :: {:ok, map()} | {:error, any()} - def parse(url), do: parse_url(url) - else - @spec parse(String.t()) :: {:ok, map()} | {:error, any()} - def parse(url) do - with {:ok, data} <- get_cached_or_parse(url), - {:ok, _} <- set_ttl_based_on_image(data, url) do - {:ok, data} - end + @spec parse(String.t()) :: {:ok, map()} | {:error, any()} + def parse(url) do + with :ok <- validate_page_url(url), + {:ok, data} <- get_cached_or_parse(url), + {:ok, _} <- set_ttl_based_on_image(data, url) do + {:ok, data} end + end - defp get_cached_or_parse(url) do - case @cachex.fetch(:rich_media_cache, url, fn -> - case parse_url(url) do - {:ok, _} = res -> - {:commit, res} - - {:error, reason} = e -> - # Unfortunately we have to log errors here, instead of doing that - # along with ttl setting at the bottom. Otherwise we can get log spam - # if more than one process was waiting for the rich media card - # while it was generated. Ideally we would set ttl here as well, - # so we don't override it number_of_waiters_on_generation - # times, but one, obviously, can't set ttl for not-yet-created entry - # and Cachex doesn't support returning ttl from the fetch callback. - log_error(url, reason) - {:commit, e} - end - end) do - {action, res} when action in [:commit, :ok] -> - case res do - {:ok, _data} = res -> - res - - {:error, reason} = e -> - if action == :commit, do: set_error_ttl(url, reason) - e - end - - {:error, e} -> - {:error, {:cachex_error, e}} - end + defp get_cached_or_parse(url) do + case @cachex.fetch(:rich_media_cache, url, fn -> + case parse_url(url) do + {:ok, _} = res -> + {:commit, res} + + {:error, reason} = e -> + # Unfortunately we have to log errors here, instead of doing that + # along with ttl setting at the bottom. Otherwise we can get log spam + # if more than one process was waiting for the rich media card + # while it was generated. Ideally we would set ttl here as well, + # so we don't override it number_of_waiters_on_generation + # times, but one, obviously, can't set ttl for not-yet-created entry + # and Cachex doesn't support returning ttl from the fetch callback. + log_error(url, reason) + {:commit, e} + end + end) do + {action, res} when action in [:commit, :ok] -> + case res do + {:ok, _data} = res -> + res + + {:error, reason} = e -> + if action == :commit, do: set_error_ttl(url, reason) + e + end + + {:error, e} -> + {:error, {:cachex_error, e}} end + end - defp set_error_ttl(_url, :body_too_large), do: :ok - defp set_error_ttl(_url, {:content_type, _}), do: :ok + defp set_error_ttl(_url, :body_too_large), do: :ok + defp set_error_ttl(_url, {:content_type, _}), do: :ok - # The TTL is not set for the errors above, since they are unlikely to change - # with time + # The TTL is not set for the errors above, since they are unlikely to change + # with time - defp set_error_ttl(url, _reason) do - ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) - @cachex.expire(:rich_media_cache, url, ttl) - :ok - end + defp set_error_ttl(url, _reason) do + ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000) + @cachex.expire(:rich_media_cache, url, ttl) + :ok + end - defp log_error(url, {:invalid_metadata, data}) do - Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) - end + defp log_error(url, {:invalid_metadata, data}) do + Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end) + end - defp log_error(url, reason) do - Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) - end + defp log_error(url, reason) do + Logger.warning(fn -> "Rich media error for #{url}: #{inspect(reason)}" end) end @doc """ @@ -166,4 +163,46 @@ defmodule Pleroma.Web.RichMedia.Parser do end) |> Map.new() end + + @spec validate_page_url(URI.t() | binary()) :: :ok | :error + defp validate_page_url(page_url) when is_binary(page_url) do + validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld]) + + page_url + |> Linkify.Parser.url?(validate_tld: validate_tld) + |> parse_uri(page_url) + end + + defp validate_page_url(%URI{host: host, scheme: "https"}) do + cond do + Linkify.Parser.ip?(host) -> + :error + + host in @config_impl.get([:rich_media, :ignore_hosts], []) -> + :error + + get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) -> + :error + + true -> + :ok + end + end + + defp validate_page_url(_), do: :error + + defp parse_uri(true, url) do + url + |> URI.parse() + |> validate_page_url + end + + defp parse_uri(_, _), do: :error + + defp get_tld(host) do + host + |> String.split(".") + |> Enum.reverse() + |> hd + end end diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do alias Pleroma.Web.Metadata alias Pleroma.Web.Router.Helpers - plug(:put_layout, :static_fe) plug(:assign_id) @page_keys ["max_id", "min_id", "limit", "since_id", "order"] diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -13,7 +13,7 @@ <div class="account-header__avatar" style="background-image: url('<%= Pleroma.User.avatar_url(@user) %>')"></div> <div class="account-header__meta"> <div class="account-header__display-name"><%= @user.name %></div> - <div class="account-header__nickname">@<%= @user.nickname %>@<%= Pleroma.User.get_host(@user) %></div> + <div class="account-header__nickname">@<%= Pleroma.User.full_nickname(@user.nickname) %></div> </div> </div> <% end %> diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Workers.BackgroundWorker do def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) when op in ["blocks_import", "follow_import", "mutes_import"] do user = User.get_cached_by_id(user_id) - {:ok, User.Import.perform(String.to_atom(op), user, identifiers)} + {:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)} end def perform(%Job{ diff --git a/mix.exs b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("2.6.51"), + version: version("2.6.52"), elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), @@ -137,7 +137,7 @@ defmodule Pleroma.Mixfile do {:calendar, "~> 1.0"}, {:cachex, "~> 3.2"}, {:poison, "~> 3.0", override: true}, - {:tesla, "~> 1.4.0", override: true}, + {:tesla, "~> 1.8.0"}, {:castore, "~> 0.1"}, {:cowlib, "~> 2.9", override: true}, {:gun, "~> 2.0.0-rc.1", override: true}, @@ -188,6 +188,7 @@ defmodule Pleroma.Mixfile do {:exile, git: "https://git.pleroma.social/pleroma/elixir-libraries/exile.git", ref: "0d6337cf68e7fbc8a093cae000955aa93b067f91"}, + {:bandit, "~> 1.2"}, ## dev & test {:ex_doc, "~> 0.22", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock @@ -1,5 +1,6 @@ %{ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, + "bandit": {:hex, :bandit, "1.2.1", "aa485b4ac175065b8e0fb5864ddd5dd7b50d52336b36f61c82f484c3718b3d15", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "27393e590a407f1b7d51c5fee4737f139fe224a30449ce25061eac70f763896b"}, "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"}, @@ -51,7 +52,7 @@ "fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"}, "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "finch": {:hex, :finch, "0.17.0", "17d06e1d44d891d20dbd437335eebe844e2426a0cd7e3a3e220b461127c73f70", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8d014a661bb6a437263d4b5abf0bcbd3cf0deb26b1e8596f2a271d22e48934c7"}, + "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm", "29bd14a88030980849c7ed2447b8db6d6c9278a28b11a44cafe41b791205440f"}, @@ -132,7 +133,8 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.2.0", "b583c3f18508f5c5561b674d16cf5d9afd2ea3c04505b7d92baaeac93c1b8260", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "9cba950e1c4733468efbe3f821841f34ac05d28e7af7798622f88ecdbbe63ea3"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, - "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, + "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"}, + "thousand_island": {:hex, :thousand_island, "1.3.2", "bc27f9afba6e1a676dd36507d42e429935a142cf5ee69b8e3f90bff1383943cd", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e085b93012cd1057b378fce40cbfbf381ff6d957a382bfdd5eca1a98eec2535"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, diff --git a/priv/gettext/ja/LC_MESSAGES/errors.po b/priv/gettext/ja/LC_MESSAGES/errors.po @@ -3,16 +3,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-09-18 09:07+0000\n" -"PO-Revision-Date: 2021-09-19 09:45+0000\n" -"Last-Translator: Ryo Ueno <r.ueno.nfive@gmail.com>\n" +"PO-Revision-Date: 2024-02-16 11:49+0000\n" +"Last-Translator: SyoBoN <syobon@syobon.net>\n" "Language-Team: Japanese <https://translate.pleroma.social/projects/pleroma/" -"pleroma/ja/>\n" +"pleroma-backend-domain-errors/ja/>\n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.6.2\n" +"X-Generator: Weblate 4.13.1\n" ## This file is a PO Template file. ## @@ -25,11 +25,11 @@ msgstr "" ## effect: edit them in PO (`.po`) files instead. ## From Ecto.Changeset.cast/4 msgid "can't be blank" -msgstr "" +msgstr "空にすることはできません" ## From Ecto.Changeset.unique_constraint/3 msgid "has already been taken" -msgstr "" +msgstr "すでに使用されています" ## From Ecto.Changeset.put_change/3 msgid "is invalid" @@ -37,7 +37,7 @@ msgstr "" ## From Ecto.Changeset.validate_format/3 msgid "has invalid format" -msgstr "" +msgstr "書式が正しくありません" ## From Ecto.Changeset.validate_subset/3 msgid "has an invalid entry" @@ -45,7 +45,7 @@ msgstr "" ## From Ecto.Changeset.validate_exclusion/3 msgid "is reserved" -msgstr "" +msgstr "予約されています" ## From Ecto.Changeset.validate_confirmation/3 msgid "does not match confirmation" @@ -61,23 +61,23 @@ msgstr "" ## From Ecto.Changeset.validate_length/3 msgid "should be %{count} character(s)" msgid_plural "should be %{count} character(s)" -msgstr[0] "" +msgstr[0] "%{count}文字である必要があります" msgid "should have %{count} item(s)" msgid_plural "should have %{count} item(s)" -msgstr[0] "" +msgstr[0] "%{count}個の要素を含む必要があります" msgid "should be at least %{count} character(s)" msgid_plural "should be at least %{count} character(s)" -msgstr[0] "" +msgstr[0] "%{count}文字以上である必要があります" msgid "should have at least %{count} item(s)" msgid_plural "should have at least %{count} item(s)" -msgstr[0] "" +msgstr[0] "%{count}個以上の要素を含む必要があります" msgid "should be at most %{count} character(s)" msgid_plural "should be at most %{count} character(s)" -msgstr[0] "" +msgstr[0] "%{count}文字以下である必要があります" msgid "should have at most %{count} item(s)" msgid_plural "should have at most %{count} item(s)" @@ -102,7 +102,7 @@ msgstr "%{number}でなければなりません" #: lib/pleroma/web/common_api/common_api.ex:505 #, elixir-format msgid "Account not found" -msgstr "アカウントがありません" +msgstr "アカウントが見つかりません" #: lib/pleroma/web/common_api/common_api.ex:339 #, elixir-format @@ -226,7 +226,7 @@ msgstr "" #: lib/pleroma/web/common_api/utils.ex:414 #, elixir-format msgid "Invalid password." -msgstr "" +msgstr "パスワードが間違っています。" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 #, elixir-format @@ -236,7 +236,7 @@ msgstr "" #: lib/pleroma/web/twitter_api/twitter_api.ex:109 #, elixir-format msgid "Kocaptcha service unavailable" -msgstr "" +msgstr "Kocaptchaが利用できません" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 #, elixir-format @@ -259,12 +259,12 @@ msgstr "" #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #, elixir-format msgid "Not found" -msgstr "" +msgstr "見つかりません" #: lib/pleroma/web/common_api/common_api.ex:331 #, elixir-format msgid "Poll's author can't vote" -msgstr "" +msgstr "作成者は投票できません" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 @@ -284,17 +284,17 @@ msgstr "" #: lib/pleroma/web/common_api/activity_draft.ex:107 #, elixir-format msgid "The message visibility must be direct" -msgstr "" +msgstr "公開範囲は「ダイレクト」でなければいけません" #: lib/pleroma/web/common_api/utils.ex:573 #, elixir-format msgid "The status is over the character limit" -msgstr "" +msgstr "文字数制限を超過しています" #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #, elixir-format msgid "This resource requires authentication." -msgstr "" +msgstr "認証が必要です。" #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #, elixir-format @@ -304,7 +304,7 @@ msgstr "" #: lib/pleroma/web/common_api/common_api.ex:356 #, elixir-format msgid "Too many choices" -msgstr "" +msgstr "選択肢が多すぎます" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 #, elixir-format @@ -320,7 +320,7 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:308 #, elixir-format msgid "Your account is currently disabled" -msgstr "" +msgstr "あなたのアカウントは無効化されています" #: lib/pleroma/web/oauth/oauth_controller.ex:183 #: lib/pleroma/web/oauth/oauth_controller.ex:331 @@ -331,23 +331,23 @@ msgstr "" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 #, elixir-format msgid "can't read inbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "%{nickname}のinboxを%{as_nickname}として閲覧することはできません" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 #, elixir-format msgid "can't update outbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "%{nickname}のoutboxを%{as_nickname}として更新することはできません" #: lib/pleroma/web/common_api/common_api.ex:471 #, elixir-format msgid "conversation is already muted" -msgstr "" +msgstr "この会話はミュート済みです" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 #, elixir-format msgid "error" -msgstr "" +msgstr "エラー" #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 #, elixir-format @@ -357,7 +357,7 @@ msgstr "" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 #, elixir-format msgid "not found" -msgstr "" +msgstr "見つかりません" #: lib/pleroma/web/oauth/oauth_controller.ex:394 #, elixir-format @@ -382,28 +382,28 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:410 #, elixir-format msgid "Failed to authenticate: %{message}." -msgstr "" +msgstr "認証に失敗しました: %{message}。" #: lib/pleroma/web/oauth/oauth_controller.ex:441 #, elixir-format msgid "Failed to set up user account." -msgstr "" +msgstr "ユーザーアカウントの設定に失敗しました。" #: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #, elixir-format msgid "Insufficient permissions: %{permissions}." -msgstr "" +msgstr "%{permissions}の権限が必要です。" #: lib/pleroma/plugs/uploaded_media.ex:104 #, elixir-format msgid "Internal Error" -msgstr "" +msgstr "内部エラー" #: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:29 #, elixir-format msgid "Invalid Username/Password" -msgstr "" +msgstr "ユーザー名/パスワードが間違っています" #: lib/pleroma/web/twitter_api/twitter_api.ex:118 #, elixir-format @@ -423,7 +423,7 @@ msgstr "" #: lib/pleroma/web/oauth/fallback_controller.ex:14 #, elixir-format msgid "Unknown error, please check the details and try again." -msgstr "" +msgstr "不明なエラーです。詳細を確認し、再度試行してください。" #: lib/pleroma/web/oauth/oauth_controller.ex:119 #: lib/pleroma/web/oauth/oauth_controller.ex:158 @@ -449,17 +449,17 @@ msgstr "" #: lib/pleroma/web/twitter_api/twitter_api.ex:103 #, elixir-format msgid "CAPTCHA Error" -msgstr "" +msgstr "CAPTCHAエラー" #: lib/pleroma/web/common_api/common_api.ex:290 #, elixir-format msgid "Could not add reaction emoji" -msgstr "" +msgstr "リアクションを追加できません" #: lib/pleroma/web/common_api/common_api.ex:301 #, elixir-format msgid "Could not remove reaction emoji" -msgstr "" +msgstr "リアクションを解除できません" #: lib/pleroma/web/twitter_api/twitter_api.ex:129 #, elixir-format @@ -469,7 +469,7 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #, elixir-format msgid "List not found" -msgstr "" +msgstr "リストが見つかりません" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 #, elixir-format @@ -480,7 +480,7 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:321 #, elixir-format msgid "Password reset is required" -msgstr "" +msgstr "パスワードのリセットが必要です" #: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 @@ -522,7 +522,7 @@ msgstr "" #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #, elixir-format msgid "Two-factor authentication enabled, you must use a access token." -msgstr "" +msgstr "二段階認証が有効になっています。アクセストークンが必要です。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 #, elixir-format @@ -552,7 +552,7 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #, elixir-format msgid "Web push subscription is disabled on this Pleroma instance" -msgstr "" +msgstr "このPleromaインスタンスではプッシュ通知が利用できません" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 #, elixir-format @@ -562,19 +562,19 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 #, elixir-format msgid "authorization required for timeline view" -msgstr "" +msgstr "タイムラインを閲覧するには認証が必要です" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 #, elixir-format msgid "Access denied" -msgstr "" +msgstr "アクセスが拒否されました" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 #, elixir-format msgid "This API requires an authenticated user" -msgstr "" +msgstr "このAPIを利用するには認証が必要です" #: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format msgid "User is not an admin." -msgstr "" +msgstr "ユーザーは管理者ではありません。" diff --git a/test/fixtures/ccworld-ap-bridge_note.json b/test/fixtures/ccworld-ap-bridge_note.json @@ -0,0 +1 @@ +{"@context":"https://www.w3.org/ns/activitystreams","type":"Note","id":"https://cc.mkdir.uk/ap/note/e5d1d0a1-1ab3-4498-9949-588e3fdea286","attributedTo":"https://cc.mkdir.uk/ap/acct/hiira","inReplyTo":"","quoteUrl":"","content":"おはコンー","published":"2024-01-19T22:08:05Z","to":["https://www.w3.org/ns/activitystreams#Public"],"tag":null,"attachment":[],"object":null} diff --git a/test/fixtures/rich_media/google.html b/test/fixtures/rich_media/google.html @@ -0,0 +1,12 @@ +<meta property="og:url" content="https://google.com"> +<meta property="og:type" content="website"> +<meta property="og:title" content="Google"> +<meta property="og:description" content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for."> +<meta property="og:image" content=""> + +<meta name="twitter:card" content="summary_large_image"> +<meta property="twitter:domain" content="google.com"> +<meta property="twitter:url" content="https://google.com"> +<meta name="twitter:title" content="Google"> +<meta name="twitter:description" content="Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for."> +<meta name="twitter:image" content=""> diff --git a/test/fixtures/rich_media/oembed.html b/test/fixtures/rich_media/oembed.html @@ -1,3 +1,3 @@ <link rel="alternate" type="application/json+oembed" - href="http://example.com/oembed.json" + href="https://example.com/oembed.json" title="Bacon Lollys oEmbed Profile" /> diff --git a/test/fixtures/rich_media/yahoo.html b/test/fixtures/rich_media/yahoo.html @@ -0,0 +1,12 @@ +<meta property="og:url" content="https://yahoo.com"> +<meta property="og:type" content="website"> +<meta property="og:title" content="Yahoo | Mail, Weather, Search, Politics, News, Finance, Sports & Videos"> +<meta property="og:description" content="Latest news coverage, email, free stock quotes, live scores and video are just the beginning. Discover more every day at Yahoo!"> +<meta property="og:image" content="https://s.yimg.com/cv/apiv2/social/images/yahoo_default_logo.png"> + +<meta name="twitter:card" content="summary_large_image"> +<meta property="twitter:domain" content="yahoo.com"> +<meta property="twitter:url" content="https://yahoo.com"> +<meta name="twitter:title" content="Yahoo | Mail, Weather, Search, Politics, News, Finance, Sports & Videos"> +<meta name="twitter:description" content="Latest news coverage, email, free stock quotes, live scores and video are just the beginning. Discover more every day at Yahoo!"> +<meta name="twitter:image" content="https://s.yimg.com/cv/apiv2/social/images/yahoo_default_logo.png"> diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs @@ -268,17 +268,6 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do end) end - test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do - assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}]) - - capture_log(fn -> - assert {:error, %WebSockex.RequestError{code: 401}} = - start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}]) - - Process.sleep(30) - end) - end - test "accepts valid token on client-sent event", %{token: token} do assert {:ok, pid} = start_socket() diff --git a/test/pleroma/maps_test.exs b/test/pleroma/maps_test.exs @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.MapsTest do + use Pleroma.DataCase, async: true + + alias Pleroma.Maps + + describe "filter_empty_values/1" do + assert %{"bar" => "b", "ray" => ["foo"], "objs" => %{"a" => "b"}} == + Maps.filter_empty_values(%{ + "foo" => nil, + "fooz" => "", + "bar" => "b", + "rei" => [], + "ray" => ["foo"], + "obj" => %{}, + "objs" => %{"a" => "b"} + }) + end +end diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -87,6 +87,32 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do assert File.exists?(fullpath) end + test "rejects invalid shortcodes", %{path: path} do + message = %{ + "type" => "Create", + "object" => %{ + "emoji" => [{"fired/fox", "https://example.org/emoji/firedfox"}], + "actor" => "https://example.org/users/admin" + } + } + + fullpath = Path.join(path, "fired/fox.png") + + Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + end) + + clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) + + refute "firedfox" in installed() + refute File.exists?(path) + + assert {:ok, _message} = StealEmojiPolicy.filter(message) + + refute "fired/fox" in installed() + refute File.exists?(fullpath) + end + test "reject regex shortcode", %{message: message} do refute "firedfox" in installed() diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs @@ -93,6 +93,17 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidatorTest %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) end + test "a Note from Convergence AP Bridge validates" do + insert(:user, ap_id: "https://cc.mkdir.uk/ap/acct/hiira") + + note = + "test/fixtures/ccworld-ap-bridge_note.json" + |> File.read!() + |> Jason.decode!() + + %{valid?: true} = ArticleNotePageValidator.cast_and_validate(note) + end + test "a note with an attachment should work", _ do insert(:user, %{ap_id: "https://owncast.localhost.localdomain/federation/user/streamer"}) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -336,13 +336,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do path -> Pleroma.Test.StaticConfig.get(path) end) - Tesla.Mock.mock(fn - %{ - method: :get, - url: "https://example.com/twitter-card" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")} - + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs @@ -49,6 +49,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do :chat_message_id_idempotency_key_cache, ^id -> {:ok, "123"} cache, key -> NullCache.get(cache, key) end) + |> stub(:fetch, fn :rich_media_cache, _, _ -> {:ok, {:ok, %{}}} end) chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref) diff --git a/test/pleroma/web/rich_media/helpers_test.exs b/test/pleroma/web/rich_media/helpers_test.exs @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.HelpersTest do - use Pleroma.DataCase, async: true + use Pleroma.DataCase, async: false alias Pleroma.StaticStubbedConfigMock, as: ConfigMock alias Pleroma.Web.CommonAPI @@ -14,7 +14,7 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do import Tesla.Mock setup do - mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) ConfigMock |> stub(:get, fn @@ -83,8 +83,34 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end - # This does not seem to work. The urls are being fetched. - @tag skip: true + test "recrawls URLs on updates" do + original_url = "https://google.com/" + updated_url = "https://yahoo.com/" + + Pleroma.StaticStubbedConfigMock + |> stub(:get, fn + [:rich_media, :enabled] -> true + path -> Pleroma.Test.StaticConfig.get(path) + end) + + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "I like this site #{original_url}"}) + + assert match?( + %{page_url: ^original_url, rich_media: _}, + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + + {:ok, _} = CommonAPI.update(user, activity, %{status: "I like this site #{updated_url}"}) + + activity = Pleroma.Activity.get_by_id(activity.id) + + assert match?( + %{page_url: ^updated_url, rich_media: _}, + Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) + ) + end + test "refuses to crawl URLs of private network from posts" do user = insert(:user) @@ -102,10 +128,10 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do path -> Pleroma.Test.StaticConfig.get(path) end) - assert %{} = Helpers.fetch_data_for_activity(activity) - assert %{} = Helpers.fetch_data_for_activity(activity2) - assert %{} = Helpers.fetch_data_for_activity(activity3) - assert %{} = Helpers.fetch_data_for_activity(activity4) - assert %{} = Helpers.fetch_data_for_activity(activity5) + assert %{} == Helpers.fetch_data_for_activity(activity) + assert %{} == Helpers.fetch_data_for_activity(activity2) + assert %{} == Helpers.fetch_data_for_activity(activity3) + assert %{} == Helpers.fetch_data_for_activity(activity4) + assert %{} == Helpers.fetch_data_for_activity(activity5) end end diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs @@ -3,95 +3,26 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.RichMedia.ParserTest do - use ExUnit.Case, async: true + use Pleroma.DataCase, async: false alias Pleroma.Web.RichMedia.Parser - setup do - Tesla.Mock.mock(fn - %{ - method: :get, - url: "http://example.com/ogp" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")} - - %{ - method: :get, - url: "http://example.com/non-ogp" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")} - - %{ - method: :get, - url: "http://example.com/ogp-missing-title" - } -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/rich_media/ogp-missing-title.html") - } - - %{ - method: :get, - url: "http://example.com/twitter-card" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")} - - %{ - method: :get, - url: "http://example.com/oembed" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")} - - %{ - method: :get, - url: "http://example.com/oembed.json" - } -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")} - - %{method: :get, url: "http://example.com/empty"} -> - %Tesla.Env{status: 200, body: "hello"} + import Tesla.Mock - %{method: :get, url: "http://example.com/malformed"} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")} - - %{method: :get, url: "http://example.com/error"} -> - {:error, :overload} - - %{ - method: :head, - url: "http://example.com/huge-page" - } -> - %Tesla.Env{ - status: 200, - headers: [{"content-length", "2000001"}, {"content-type", "text/html"}] - } - - %{ - method: :head, - url: "http://example.com/pdf-file" - } -> - %Tesla.Env{ - status: 200, - headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}] - } - - %{method: :head} -> - %Tesla.Env{status: 404, body: "", headers: []} - end) - - :ok + setup do + mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) end test "returns error when no metadata present" do - assert {:error, _} = Parser.parse("http://example.com/empty") + assert {:error, _} = Parser.parse("https://example.com/empty") end test "doesn't just add a title" do - assert {:error, {:invalid_metadata, _}} = Parser.parse("http://example.com/non-ogp") + assert {:error, {:invalid_metadata, _}} = Parser.parse("https://example.com/non-ogp") end test "parses ogp" do - assert Parser.parse("http://example.com/ogp") == + assert Parser.parse("https://example.com/ogp") == {:ok, %{ "image" => "http://ia.media-imdb.com/images/rock.jpg", @@ -99,12 +30,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "description" => "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "type" => "video.movie", - "url" => "http://example.com/ogp" + "url" => "https://example.com/ogp" }} end test "falls back to <title> when ogp:title is missing" do - assert Parser.parse("http://example.com/ogp-missing-title") == + assert Parser.parse("https://example.com/ogp-missing-title") == {:ok, %{ "image" => "http://ia.media-imdb.com/images/rock.jpg", @@ -112,12 +43,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "description" => "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "type" => "video.movie", - "url" => "http://example.com/ogp-missing-title" + "url" => "https://example.com/ogp-missing-title" }} end test "parses twitter card" do - assert Parser.parse("http://example.com/twitter-card") == + assert Parser.parse("https://example.com/twitter-card") == {:ok, %{ "card" => "summary", @@ -125,12 +56,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg", "title" => "Small Island Developing States Photo Submission", "description" => "View the album on Flickr.", - "url" => "http://example.com/twitter-card" + "url" => "https://example.com/twitter-card" }} end test "parses OEmbed and filters HTML tags" do - assert Parser.parse("http://example.com/oembed") == + assert Parser.parse("https://example.com/oembed") == {:ok, %{ "author_name" => "\u202E\u202D\u202Cbees\u202C", @@ -150,7 +81,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do "thumbnail_width" => 150, "title" => "Bacon Lollys", "type" => "photo", - "url" => "http://example.com/oembed", + "url" => "https://example.com/oembed", "version" => "1.0", "web_page" => "https://www.flickr.com/photos/bees/2362225867/", "web_page_short_url" => "https://flic.kr/p/4AK2sc", @@ -159,18 +90,18 @@ defmodule Pleroma.Web.RichMedia.ParserTest do end test "rejects invalid OGP data" do - assert {:error, _} = Parser.parse("http://example.com/malformed") + assert {:error, _} = Parser.parse("https://example.com/malformed") end test "returns error if getting page was not successful" do - assert {:error, :overload} = Parser.parse("http://example.com/error") + assert {:error, :overload} = Parser.parse("https://example.com/error") end test "does a HEAD request to check if the body is too large" do - assert {:error, :body_too_large} = Parser.parse("http://example.com/huge-page") + assert {:error, :body_too_large} = Parser.parse("https://example.com/huge-page") end test "does a HEAD request to check if the body is html" do - assert {:error, {:content_type, _}} = Parser.parse("http://example.com/pdf-file") + assert {:error, {:content_type, _}} = Parser.parse("https://example.com/pdf-file") end end diff --git a/test/support/cachex_proxy.ex b/test/support/cachex_proxy.ex @@ -27,9 +27,15 @@ defmodule Pleroma.CachexProxy do defdelegate fetch!(cache, key, func), to: Cachex @impl true + defdelegate fetch(cache, key, func), to: Cachex + + @impl true defdelegate expire_at(cache, str, num), to: Cachex @impl true + defdelegate expire(cache, str, num), to: Cachex + + @impl true defdelegate exists?(cache, key), to: Cachex @impl true diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex @@ -1059,7 +1059,7 @@ defmodule HttpRequestMock do }} end - def get("http://example.com/malformed", _, _, _) do + def get("https://example.com/malformed", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}} end @@ -1464,6 +1464,45 @@ defmodule HttpRequestMock do }} end + def get("https://google.com/", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/google.html")}} + end + + def get("https://yahoo.com/", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/yahoo.html")}} + end + + def get("https://example.com/error", _, _, _), do: {:error, :overload} + + def get("https://example.com/ogp-missing-title", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/rich_media/ogp-missing-title.html") + }} + end + + def get("https://example.com/oembed", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.html")}} + end + + def get("https://example.com/oembed.json", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/oembed.json")}} + end + + def get("https://example.com/twitter-card", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/twitter_card.html")}} + end + + def get("https://example.com/non-ogp", _, _, _) do + {:ok, + %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/non_ogp_embed.html")}} + end + + def get("https://example.com/empty", _, _, _) do + {:ok, %Tesla.Env{status: 200, body: "hello"}} + end + def get(url, query, body, headers) do {:error, "Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} @@ -1537,14 +1576,41 @@ defmodule HttpRequestMock do # Most of the rich media mocks are missing HEAD requests, so we just return 404. @rich_media_mocks [ + "https://example.com/empty", + "https://example.com/error", + "https://example.com/malformed", + "https://example.com/non-ogp", + "https://example.com/oembed", + "https://example.com/oembed.json", "https://example.com/ogp", "https://example.com/ogp-missing-data", - "https://example.com/twitter-card" + "https://example.com/ogp-missing-title", + "https://example.com/twitter-card", + "https://google.com/", + "https://pleroma.local/notice/9kCP7V", + "https://yahoo.com/" ] + def head(url, _query, _body, _headers) when url in @rich_media_mocks do {:ok, %Tesla.Env{status: 404, body: ""}} end + def head("https://example.com/pdf-file", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + headers: [{"content-length", "1000000"}, {"content-type", "application/pdf"}] + }} + end + + def head("https://example.com/huge-page", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + headers: [{"content-length", "2000001"}, {"content-type", "text/html"}] + }} + end + def head(url, query, body, headers) do {:error, "Mock response not implemented for HEAD #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"} diff --git a/test/support/null_cache.ex b/test/support/null_cache.ex @@ -29,6 +29,9 @@ defmodule Pleroma.NullCache do end @impl true + def fetch(_, key, func), do: func.(key) + + @impl true def get_and_update(_, _, func) do func.(nil) end @@ -37,6 +40,9 @@ defmodule Pleroma.NullCache do def expire_at(_, _, _), do: {:ok, true} @impl true + def expire(_, _, _), do: {:ok, true} + + @impl true def exists?(_, _), do: {:ok, false} @impl true