Renew HTTP signatures when following redirects #973
29 changed files with 252 additions and 510 deletions
|
|
@ -47,6 +47,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- fixed handling of inlined "featured" collections
|
||||
- fixed user endpoint serving invalid ActivityPub for minimal, authfetch-fallback responses
|
||||
- remote emoji reacts from IceShrimp.NET instances are now handled consistently and always merged with identical other emoji reactions
|
||||
- ActivityPub requests signatures are now renewed when following redirects making sure path and host actually match the final URL
|
||||
|
||||
### Changed
|
||||
- Internal and relay actors are now again represented with type "Application"
|
||||
|
|
@ -65,6 +66,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
instead of mapping them down to a boolean private/public
|
||||
- we no longer repeatedly try to deliver to explicitly deleted inboxes
|
||||
- outgoing requests may now use HTTP2 by default
|
||||
- Config option `Pleroma.Web.MediaProxy.Invalidation.Http, :options` and
|
||||
the `:http` subkey of `:media_proxy, :proxy_opts` now only accept
|
||||
adapter-related settings inside the `:adapter` subkey, no longer on the top-level
|
||||
|
||||
|
||||
## 2025.03
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ rum_enabled = System.get_env("RUM_ENABLED") == "true"
|
|||
config :pleroma, :database, rum_enabled: rum_enabled
|
||||
IO.puts("RUM enabled: #{rum_enabled}")
|
||||
|
||||
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
|
||||
|
||||
if File.exists?("./config/benchmark.secret.exs") do
|
||||
import_config "benchmark.secret.exs"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -580,7 +580,7 @@ defmodule Pleroma.Emoji.Pack do
|
|||
defp http_get(%URI{} = url), do: url |> to_string() |> http_get()
|
||||
|
||||
defp http_get(url) do
|
||||
with {:ok, %{body: body}} <- Pleroma.HTTP.get(url, [], []) do
|
||||
with {:ok, %{body: body}} <- Pleroma.HTTP.get(url) do
|
||||
Jason.decode(body)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ defmodule Pleroma.Frontend do
|
|||
url = String.replace(frontend_info["build_url"], "${ref}", frontend_info["ref"])
|
||||
|
||||
with {:ok, %{status: 200, body: zip_body}} <-
|
||||
Pleroma.HTTP.get(url, [], receive_timeout: 120_000) do
|
||||
Pleroma.HTTP.get(url, [], adapter: [receive_timeout: 120_000]) do
|
||||
unzip(zip_body, dest)
|
||||
else
|
||||
{:error, e} -> {:error, e}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Helpers.MediaHelper do
|
|||
def image_resize(url, options) do
|
||||
with executable when is_binary(executable) <- System.find_executable("convert"),
|
||||
{:ok, args} <- prepare_image_resize_args(options),
|
||||
{:ok, env} <- HTTP.get(url, [], []),
|
||||
{:ok, env} <- HTTP.get(url),
|
||||
{:ok, fifo_path} <- mkfifo() do
|
||||
args = List.flatten([fifo_path, args])
|
||||
run_fifo(fifo_path, env, executable, args)
|
||||
|
|
@ -73,7 +73,7 @@ defmodule Pleroma.Helpers.MediaHelper do
|
|||
# Note: video thumbnail is intentionally not resized (always has original dimensions)
|
||||
def video_framegrab(url) do
|
||||
with executable when is_binary(executable) <- System.find_executable("ffmpeg"),
|
||||
{:ok, env} <- HTTP.get(url, [], []),
|
||||
{:ok, env} <- HTTP.get(url),
|
||||
{:ok, fifo_path} <- mkfifo(),
|
||||
args = [
|
||||
"-y",
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ defmodule Pleroma.HTTP do
|
|||
"""
|
||||
|
||||
alias Pleroma.HTTP.AdapterHelper
|
||||
alias Pleroma.HTTP.Request
|
||||
alias Pleroma.HTTP.RequestBuilder, as: Builder
|
||||
alias Tesla.Client
|
||||
alias Tesla.Env
|
||||
|
||||
require Logger
|
||||
|
|
@ -18,6 +15,8 @@ defmodule Pleroma.HTTP do
|
|||
@type t :: __MODULE__
|
||||
@type method() :: :get | :post | :put | :delete | :head
|
||||
|
||||
@mix_env Mix.env()
|
||||
|
||||
@doc """
|
||||
Performs GET request.
|
||||
|
||||
|
|
@ -60,7 +59,7 @@ defmodule Pleroma.HTTP do
|
|||
{:ok, Env.t()} | {:error, any()}
|
||||
def request(method, url, body, headers, options) when is_binary(url) do
|
||||
uri = URI.parse(url)
|
||||
adapter_opts = AdapterHelper.options(options || [])
|
||||
adapter_opts = AdapterHelper.options(options[:adapter] || [])
|
||||
|
||||
adapter_opts =
|
||||
if uri.scheme == :https do
|
||||
|
|
@ -71,28 +70,43 @@ defmodule Pleroma.HTTP do
|
|||
|
||||
options = put_in(options[:adapter], adapter_opts)
|
||||
params = options[:params] || []
|
||||
request = build_request(method, headers, options, url, body, params)
|
||||
client = Tesla.client([Tesla.Middleware.FollowRedirects, Tesla.Middleware.Telemetry])
|
||||
options = options |> Keyword.delete(:params)
|
||||
headers = maybe_add_user_agent(headers)
|
||||
|
||||
client =
|
||||
Tesla.client([
|
||||
Tesla.Middleware.FollowRedirects,
|
||||
Pleroma.HTTP.Middleware.HTTPSignature,
|
||||
Tesla.Middleware.Telemetry
|
||||
])
|
||||
|
||||
Logger.debug("Outbound: #{method} #{url}")
|
||||
request(client, request)
|
||||
|
||||
Tesla.request(client,
|
||||
method: method,
|
||||
url: url,
|
||||
query: params,
|
||||
headers: headers,
|
||||
body: body,
|
||||
opts: options
|
||||
)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error("Failed to fetch #{url}: #{Exception.format(:error, e, __STACKTRACE__)}")
|
||||
{:error, :fetch_error}
|
||||
end
|
||||
|
||||
@spec request(Client.t(), keyword()) :: {:ok, Env.t()} | {:error, any()}
|
||||
def request(client, request), do: Tesla.request(client, request)
|
||||
|
||||
defp build_request(method, headers, options, url, body, params) do
|
||||
Builder.new()
|
||||
|> Builder.method(method)
|
||||
|> Builder.headers(headers)
|
||||
|> Builder.opts(options)
|
||||
|> Builder.url(url)
|
||||
|> Builder.add_param(:body, :body, body)
|
||||
|> Builder.add_param(:query, :query, params)
|
||||
|> Builder.convert_to_keyword()
|
||||
if @mix_env == :test do
|
||||
defp maybe_add_user_agent(headers) do
|
||||
with true <- Pleroma.Config.get([:http, :send_user_agent]) do
|
||||
[{"user-agent", Pleroma.Application.user_agent()} | headers]
|
||||
else
|
||||
_ ->
|
||||
headers
|
||||
end
|
||||
end
|
||||
else
|
||||
defp maybe_add_user_agent(headers),
|
||||
do: [{"user-agent", Pleroma.Application.user_agent()} | headers]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
135
lib/pleroma/http/middleware/httpsignature.ex
Normal file
135
lib/pleroma/http/middleware/httpsignature.ex
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Akkoma: Magically expressive social media
|
||||
# Copyright © 2025 Akkoma Authors <https://akkoma.dev/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.Middleware.HTTPSignature do
|
||||
alias Pleroma.User.SigningKey
|
||||
alias Pleroma.Signature
|
||||
|
||||
require Logger
|
||||
|
||||
@behaviour Tesla.Middleware
|
||||
|
||||
@moduledoc """
|
||||
Adds a HTTP signature and related headers to requests, if a signing key is set in the request env.
|
||||
If any other middleware can update the target location (e.g. redirects) this MUST be placed after all of them!
|
||||
|
||||
(Note: the third argument holds static middleware options from client creation)
|
||||
"""
|
||||
|
||||
@doc """
|
||||
If logging raw Tesla.Env use this if you wish to redact signing key details
|
||||
"""
|
||||
def redact_keys(env) do
|
||||
case get_in(env, [:opts, :httpsig, :signing_key]) do
|
||||
nil -> env
|
||||
key -> put_in(env, [:opts, :httpsig, :signing_key], redact_key_details(key))
|
||||
end
|
||||
end
|
||||
|
||||
defp redact_key_details(%SigningKey{key_id: id}), do: id
|
||||
defp redact_key_details(key), do: key
|
||||
|
||||
@impl true
|
||||
def call(env, next, _options) do
|
||||
env = maybe_sign(env)
|
||||
Tesla.run(env, next)
|
||||
end
|
||||
|
||||
defp maybe_sign(env) do
|
||||
case Keyword.get(env.opts, :httpsig) do
|
||||
%{signing_key: %SigningKey{} = key} ->
|
||||
set_signature_headers(env, key)
|
||||
|
||||
_ ->
|
||||
env
|
||||
end
|
||||
end
|
||||
|
||||
defp set_signature_headers(env, key) do
|
||||
Logger.debug("Signing request to: #{env.url}")
|
||||
{http_headers, signing_headers} = collect_headers_for_signature(env)
|
||||
signature = Signature.sign(key, signing_headers, has_body: has_body(env))
|
||||
set_headers(env, [{"signature", signature} | http_headers])
|
||||
end
|
||||
|
||||
defp has_body(%{body: body}) when body in [nil, ""], do: false
|
||||
defp has_body(_), do: true
|
||||
|
||||
defp set_headers(env, []), do: env
|
||||
|
||||
defp set_headers(env, [{key, val} | rest]) do
|
||||
headers = :proplists.delete(key, env.headers)
|
||||
headers = [{key, val} | headers]
|
||||
set_headers(%{env | headers: headers}, rest)
|
||||
end
|
||||
|
||||
# Returns tuple.
|
||||
# First element is headers+values which need to be added to the HTTP request.
|
||||
# Second element are all headers to be used for signing, including already existing and pseudo headers.
|
||||
defp collect_headers_for_signature(env) do
|
||||
{request_target, host} = get_request_target_and_host(env)
|
||||
date = http_date()
|
||||
|
||||
# content-length is always automatically set later on
|
||||
# since they are needed to establish working connection.
|
||||
# Similarly host will always be set for HTTP/1, and technically may be omitted for HTTP/2+
|
||||
# but Tesla doesn’t handle it well if we preset it ourselves (and seems to set it even for HTTP/2 anyway)
|
||||
http_headers = [{"date", date}]
|
||||
|
||||
signing_headers = %{
|
||||
"(request-target)" => request_target,
|
||||
"host" => host,
|
||||
"date" => date
|
||||
}
|
||||
|
||||
if has_body(env) do
|
||||
append_body_headers(env, http_headers, signing_headers)
|
||||
else
|
||||
{http_headers, signing_headers}
|
||||
end
|
||||
end
|
||||
|
||||
defp append_body_headers(env, http_headers, signing_headers) do
|
||||
content_length = byte_size(env.body)
|
||||
digest = digest_value(env)
|
||||
|
||||
http_headers = [{"digest", digest} | http_headers]
|
||||
|
||||
signing_headers =
|
||||
Map.merge(signing_headers, %{
|
||||
"digest" => digest,
|
||||
"content-length" => content_length
|
||||
})
|
||||
|
||||
{http_headers, signing_headers}
|
||||
end
|
||||
|
||||
defp get_request_target_and_host(env) do
|
||||
uri = URI.parse(env.url)
|
||||
rt = "#{env.method} #{uri.path}"
|
||||
host = host_from_uri(uri)
|
||||
{rt, host}
|
||||
end
|
||||
|
||||
defp digest_value(env) do
|
||||
# case Tesla.get_header(env, "digest")
|
||||
encoded_hash = :crypto.hash(:sha256, env.body) |> Base.encode64()
|
||||
"SHA-256=" <> encoded_hash
|
||||
end
|
||||
|
||||
defp host_from_uri(%URI{port: port, scheme: scheme, host: host}) do
|
||||
# https://httpwg.org/specs/rfc9110.html#field.host
|
||||
# https://www.rfc-editor.org/rfc/rfc3986.html#section-3.2.3
|
||||
if port == URI.default_port(scheme) do
|
||||
host
|
||||
else
|
||||
"#{host}:#{port}"
|
||||
end
|
||||
end
|
||||
|
||||
defp http_date() do
|
||||
now = NaiveDateTime.utc_now()
|
||||
Timex.lformat!(now, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en")
|
||||
end
|
||||
end
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.Request do
|
||||
@moduledoc """
|
||||
Request struct.
|
||||
"""
|
||||
defstruct method: :get, url: "", query: [], headers: [], body: "", opts: []
|
||||
|
||||
@type method :: :head | :get | :delete | :trace | :options | :post | :put | :patch
|
||||
@type url :: String.t()
|
||||
@type headers :: [{String.t(), String.t()}]
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
method: method(),
|
||||
url: url(),
|
||||
query: keyword(),
|
||||
headers: headers(),
|
||||
body: String.t(),
|
||||
opts: keyword()
|
||||
}
|
||||
end
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.RequestBuilder do
|
||||
@moduledoc """
|
||||
Helper functions for building Tesla requests
|
||||
"""
|
||||
|
||||
alias Pleroma.HTTP.Request
|
||||
alias Tesla.Multipart
|
||||
|
||||
@mix_env Mix.env()
|
||||
|
||||
@doc """
|
||||
Creates new request
|
||||
"""
|
||||
@spec new(Request.t()) :: Request.t()
|
||||
def new(%Request{} = request \\ %Request{}), do: request
|
||||
|
||||
@doc """
|
||||
Specify the request method when building a request
|
||||
"""
|
||||
@spec method(Request.t(), Request.method()) :: Request.t()
|
||||
def method(request, m), do: %{request | method: m}
|
||||
|
||||
@doc """
|
||||
Specify the request method when building a request
|
||||
"""
|
||||
@spec url(Request.t(), Request.url()) :: Request.t()
|
||||
def url(request, u), do: %{request | url: u}
|
||||
|
||||
@doc """
|
||||
Add headers to the request
|
||||
"""
|
||||
@spec headers(Request.t(), Request.headers()) :: Request.t()
|
||||
def headers(request, headers) do
|
||||
headers_list = maybe_add_user_agent(headers, @mix_env)
|
||||
|
||||
%{request | headers: headers_list}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Add custom, per-request middleware or adapter options to the request
|
||||
"""
|
||||
@spec opts(Request.t(), keyword()) :: Request.t()
|
||||
def opts(request, options), do: %{request | opts: options}
|
||||
|
||||
@doc """
|
||||
Add optional parameters to the request
|
||||
"""
|
||||
@spec add_param(Request.t(), atom(), atom(), 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
|
||||
request
|
||||
|> Map.put(:body, Multipart.new())
|
||||
|> Map.update!(
|
||||
:body,
|
||||
&Multipart.add_field(
|
||||
&1,
|
||||
key,
|
||||
Jason.encode!(value),
|
||||
headers: [{"content-type", "application/json"}]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def add_param(request, :file, name, path) do
|
||||
request
|
||||
|> Map.put(:body, Multipart.new())
|
||||
|> Map.update!(:body, &Multipart.add_file(&1, path, name: name))
|
||||
end
|
||||
|
||||
def add_param(request, :form, name, value) do
|
||||
Map.update(request, :body, %{name => value}, &Map.put(&1, name, value))
|
||||
end
|
||||
|
||||
def add_param(request, location, key, value) do
|
||||
Map.update(request, location, [{key, value}], &(&1 ++ [{key, value}]))
|
||||
end
|
||||
|
||||
def convert_to_keyword(request) do
|
||||
request
|
||||
|> Map.from_struct()
|
||||
|> Enum.into([])
|
||||
end
|
||||
|
||||
defp maybe_add_user_agent(headers, :test) do
|
||||
with true <- Pleroma.Config.get([:http, :send_user_agent]) do
|
||||
[{"user-agent", Pleroma.Application.user_agent()} | headers]
|
||||
else
|
||||
_ ->
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_add_user_agent(headers, _),
|
||||
do: [{"user-agent", Pleroma.Application.user_agent()} | headers]
|
||||
end
|
||||
|
|
@ -10,15 +10,15 @@ defmodule Pleroma.HTTP.Tzdata do
|
|||
alias Pleroma.HTTP
|
||||
|
||||
@impl true
|
||||
def get(url, headers, options) do
|
||||
with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
|
||||
def get(url, headers, _options) do
|
||||
with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers) do
|
||||
{:ok, {env.status, env.headers, env.body}}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def head(url, headers, options) do
|
||||
with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
|
||||
def head(url, headers, _options) do
|
||||
with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers) do
|
||||
{:ok, {env.status, env.headers}}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
defmodule Pleroma.HTTP.WebPush do
|
||||
@moduledoc false
|
||||
|
||||
def post(url, payload, headers, options \\ []) do
|
||||
def post(url, payload, headers, _options) do
|
||||
list_headers = Map.to_list(headers)
|
||||
Pleroma.HTTP.post(url, payload, list_headers, options)
|
||||
Pleroma.HTTP.post(url, payload, list_headers)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -242,7 +242,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
{:ok,
|
||||
Enum.find(links, &(&1["rel"] == "http://nodeinfo.diaspora.software/ns/schema/2.0"))},
|
||||
{:ok, %Tesla.Env{body: data}} <-
|
||||
Pleroma.HTTP.get(href, [{"accept", "application/json"}], []),
|
||||
Pleroma.HTTP.get(href, [{"accept", "application/json"}]),
|
||||
{:length, true} <- {:length, String.length(data) < 50_000},
|
||||
{:ok, nodeinfo} <- Jason.decode(data) do
|
||||
nodeinfo
|
||||
|
|
@ -270,7 +270,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
with true <- Pleroma.Config.get([:instances_favicons, :enabled]),
|
||||
{_, true} <- {:reachable, reachable?(instance_uri.host)},
|
||||
{:ok, %Tesla.Env{body: html}} <-
|
||||
Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}], []),
|
||||
Pleroma.HTTP.get(to_string(instance_uri), [{"accept", "text/html"}]),
|
||||
{_, [favicon_rel | _]} when is_binary(favicon_rel) <-
|
||||
{:parse, html |> Floki.parse_document!() |> Floki.attribute("link[rel=icon]", "href")},
|
||||
{_, favicon} when is_binary(favicon) <-
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ defmodule Pleroma.Object.Fetcher do
|
|||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.Web.ActivityPub.InternalFetchActor
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
|
@ -227,36 +226,6 @@ defmodule Pleroma.Object.Fetcher do
|
|||
|> Maps.put_if_present("bcc", data["bcc"])
|
||||
end
|
||||
|
||||
defp make_signature(id, date) do
|
||||
uri = URI.parse(id)
|
||||
|
||||
signature =
|
||||
InternalFetchActor.get_actor()
|
||||
|> Signature.sign(%{
|
||||
"(request-target)" => "get #{uri.path}",
|
||||
"host" => uri.host,
|
||||
"date" => date
|
||||
})
|
||||
|
||||
{"signature", signature}
|
||||
end
|
||||
|
||||
defp sign_fetch(headers, id, date) do
|
||||
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
|
||||
[make_signature(id, date) | headers]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_date_fetch(headers, date) do
|
||||
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
|
||||
[{"date", date} | headers]
|
||||
else
|
||||
headers
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches arbitrary remote object and performs basic safety and authenticity checks.
|
||||
When the fetch URL is known to already be a canonical AP id, checks are stricter.
|
||||
|
|
@ -402,20 +371,25 @@ defmodule Pleroma.Object.Fetcher do
|
|||
|
||||
@doc "Do NOT use; only public for use in tests"
|
||||
def get_object(id) do
|
||||
date = Pleroma.Signature.signed_date()
|
||||
|
||||
headers =
|
||||
[
|
||||
# The first is required by spec, the second provided as a fallback for buggy implementations
|
||||
{"accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""},
|
||||
{"accept", "application/activity+json"}
|
||||
]
|
||||
|> maybe_date_fetch(date)
|
||||
|> sign_fetch(id, date)
|
||||
|
||||
http_opts =
|
||||
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
|
||||
signing_actor = InternalFetchActor.get_actor() |> Pleroma.User.SigningKey.load_key()
|
||||
signing_key = signing_actor.signing_key
|
||||
[httpsig: %{signing_key: signing_key}]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
with {:ok, %{body: body, status: code, headers: headers, url: final_url}}
|
||||
when code in 200..299 <-
|
||||
HTTP.Backoff.get(id, headers),
|
||||
HTTP.Backoff.get(id, headers, http_opts),
|
||||
{:has_content_type, {_, content_type}} <-
|
||||
{:has_content_type, List.keyfind(headers, "content-type", 0)},
|
||||
{:parse_content_type, {:ok, "application", subtype, type_params}} <-
|
||||
|
|
@ -443,6 +417,13 @@ defmodule Pleroma.Object.Fetcher do
|
|||
{:ok, %{status: code}} when code in [404, 410] ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:ok, %{status: code, headers: headers}} ->
|
||||
{:error, {:http_error, code, headers}}
|
||||
|
||||
# connection/protocol-related error
|
||||
{:ok, %Tesla.Env{} = env} ->
|
||||
{:error, {:http_error, :connect, Pleroma.HTTP.Middleware.HTTPSignature.redact_keys(env)}}
|
||||
|
||||
{:error, e} ->
|
||||
{:error, e}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy.Client do
|
||||
@type status :: pos_integer()
|
||||
@type header_name :: String.t()
|
||||
@type header_value :: String.t()
|
||||
@type headers :: [{header_name(), header_value()}]
|
||||
|
||||
@callback request(atom(), String.t(), headers(), String.t(), list()) ::
|
||||
{:ok, status(), headers(), reference() | map()}
|
||||
| {:ok, status(), headers()}
|
||||
| {:ok, reference()}
|
||||
| {:error, term()}
|
||||
|
||||
@callback stream_body(map()) :: {:ok, binary(), map()} | :done | {:error, atom() | String.t()}
|
||||
|
||||
@callback close(reference() | pid() | map()) :: :ok
|
||||
end
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy.Client.Tesla do
|
||||
@behaviour Pleroma.ReverseProxy.Client
|
||||
|
||||
@type headers() :: [{String.t(), String.t()}]
|
||||
@type status() :: pos_integer()
|
||||
|
||||
@spec request(atom(), String.t(), headers(), String.t(), keyword()) ::
|
||||
{:ok, status(), headers}
|
||||
| {:ok, status(), headers, map()}
|
||||
| {:error, atom() | String.t()}
|
||||
| no_return()
|
||||
|
||||
@impl true
|
||||
def request(method, url, headers, body, opts \\ []) do
|
||||
check_adapter()
|
||||
|
||||
opts = Keyword.put(opts, :body_as, :chunks)
|
||||
|
||||
with {:ok, response} <-
|
||||
Pleroma.HTTP.request(
|
||||
method,
|
||||
url,
|
||||
body,
|
||||
headers,
|
||||
opts
|
||||
) do
|
||||
if is_map(response.body) and method != :head do
|
||||
{:ok, response.status, response.headers, response.body}
|
||||
else
|
||||
{:ok, response.status, response.headers}
|
||||
end
|
||||
else
|
||||
{:error, error} -> {:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec stream_body(map()) ::
|
||||
{:ok, binary(), map()} | {:error, atom() | String.t()} | :done | no_return()
|
||||
def stream_body(%{pid: _pid, fin: true}) do
|
||||
:done
|
||||
end
|
||||
|
||||
def stream_body(client) do
|
||||
case read_chunk!(client) do
|
||||
{:fin, body} ->
|
||||
{:ok, body, Map.put(client, :fin, true)}
|
||||
|
||||
{:nofin, part} ->
|
||||
{:ok, part, client}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp read_chunk!(%{pid: pid, stream: stream, opts: opts}) do
|
||||
adapter = check_adapter()
|
||||
adapter.read_chunk(pid, stream, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
@spec close(map) :: :ok | no_return()
|
||||
def close(%{pid: _pid}) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp check_adapter do
|
||||
adapter = Application.get_env(:tesla, :adapter)
|
||||
|
||||
adapter
|
||||
end
|
||||
end
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy.Client.Wrapper do
|
||||
@moduledoc "Meta-client that calls the appropriate client from the config."
|
||||
@behaviour Pleroma.ReverseProxy.Client
|
||||
|
||||
@impl true
|
||||
def request(method, url, headers, body \\ "", opts \\ []) do
|
||||
client().request(method, url, headers, body, opts)
|
||||
end
|
||||
|
||||
@impl true
|
||||
def stream_body(ref), do: client().stream_body(ref)
|
||||
|
||||
@impl true
|
||||
def close(ref), do: client().close(ref)
|
||||
|
||||
defp client do
|
||||
:tesla
|
||||
|> Application.get_env(:adapter)
|
||||
|> client()
|
||||
end
|
||||
|
||||
defp client({Tesla.Adapter.Finch, _}), do: Pleroma.ReverseProxy.Client.Tesla
|
||||
defp client(_), do: Pleroma.Config.get!(Pleroma.ReverseProxy.Client)
|
||||
end
|
||||
|
|
@ -62,20 +62,16 @@ defmodule Pleroma.Signature do
|
|||
end
|
||||
end
|
||||
|
||||
def sign(%User{} = user, headers, opts \\ []) do
|
||||
with {:ok, private_key} <- SigningKey.private_key(user) do
|
||||
def sign(%SigningKey{} = key, headers, opts \\ []) do
|
||||
with {:ok, private_key_binary} <- SigningKey.private_key_binary(key) do
|
||||
HTTPSignatures.sign(
|
||||
%HTTPKey{key: private_key},
|
||||
SigningKey.local_key_id(user.ap_id),
|
||||
%HTTPKey{key: private_key_binary},
|
||||
key.key_id,
|
||||
headers,
|
||||
opts
|
||||
)
|
||||
else
|
||||
_ -> raise "Tried to sign with #{key.key_id} but it has no private key!"
|
||||
end
|
||||
end
|
||||
|
||||
def signed_date, do: signed_date(NaiveDateTime.utc_now())
|
||||
|
||||
def signed_date(%NaiveDateTime{} = date) do
|
||||
Timex.lformat!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -136,24 +136,22 @@ defmodule Pleroma.User.SigningKey do
|
|||
{:error, "key not found"}
|
||||
end
|
||||
|
||||
@spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
|
||||
@spec private_key_binary(__MODULE__) :: {:ok, binary()} | {:error, String.t()}
|
||||
@doc """
|
||||
Given a user, return the private key for that user in binary format.
|
||||
Given a key, return the corresponding private key in binary format.
|
||||
"""
|
||||
def private_key(%User{} = user) do
|
||||
case Repo.preload(user, :signing_key) do
|
||||
%{signing_key: %__MODULE__{private_key: private_key_pem}} ->
|
||||
key =
|
||||
private_key_pem
|
||||
|> :public_key.pem_decode()
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
def private_key_binary(%__MODULE__{private_key: private_key_pem}) do
|
||||
key =
|
||||
private_key_pem
|
||||
|> :public_key.pem_decode()
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
{:ok, key}
|
||||
{:ok, key}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, "key not found"}
|
||||
end
|
||||
def private_key_binary(%__MODULE__{} = key) do
|
||||
{:error, "key #{key.key_id} has no private key"}
|
||||
end
|
||||
|
||||
@spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
|
||||
require Logger
|
||||
|
||||
@adapter_options [
|
||||
receive_timeout: 10_000
|
||||
@http_options [
|
||||
adapter: [receive_timeout: 10_000]
|
||||
]
|
||||
|
||||
@impl true
|
||||
|
|
@ -36,7 +36,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
|
|||
end
|
||||
end
|
||||
|
||||
defp fetch(url), do: HTTP.get(url, [], @adapter_options)
|
||||
defp fetch(url), do: HTTP.get(url, [], @http_options)
|
||||
|
||||
defp preload(%{"object" => %{"attachment" => attachments}} = _message) do
|
||||
Enum.each(attachments, fn
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
|
|||
|
||||
defp is_remote_size_within_limit?(url) do
|
||||
with {:ok, %{status: status, headers: headers} = _response} when status in 200..299 <-
|
||||
Pleroma.HTTP.request(:head, url, nil, [], []) do
|
||||
Pleroma.HTTP.head(url) do
|
||||
content_length = get_int_header(headers, "content-length")
|
||||
size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit)
|
||||
|
||||
|
|
|
|||
|
|
@ -51,34 +51,15 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
%{"inbox" => inbox, "json" => json, "actor" => %User{} = actor, "id" => id} = params
|
||||
) do
|
||||
Logger.debug("Federating #{id} to #{inbox}")
|
||||
uri = %{path: path} = URI.parse(inbox)
|
||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||
|
||||
date = Pleroma.Signature.signed_date()
|
||||
|
||||
signature =
|
||||
Pleroma.Signature.sign(
|
||||
actor,
|
||||
%{
|
||||
"(request-target)" => "post #{path}",
|
||||
"host" => signature_host(uri),
|
||||
"content-length" => byte_size(json),
|
||||
"digest" => digest,
|
||||
"date" => date
|
||||
},
|
||||
has_body: true
|
||||
)
|
||||
signing_key = Pleroma.User.SigningKey.load_key(actor).signing_key
|
||||
|
||||
with {:ok, %{status: code}} = result when code in 200..299 <-
|
||||
HTTP.post(
|
||||
inbox,
|
||||
json,
|
||||
[
|
||||
{"Content-Type", "application/activity+json"},
|
||||
{"Date", date},
|
||||
{"signature", signature},
|
||||
{"digest", digest}
|
||||
]
|
||||
[{"content-type", "application/activity+json"}],
|
||||
httpsig: %{signing_key: signing_key}
|
||||
) do
|
||||
if not Map.has_key?(params, "unreachable_since") || params["unreachable_since"] do
|
||||
Instances.set_reachable(inbox)
|
||||
|
|
@ -88,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
else
|
||||
{_post_result, response} ->
|
||||
unless params["unreachable_since"], do: Instances.set_unreachable(inbox)
|
||||
{:error, response}
|
||||
{:error, format_error_response(response)}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,13 +82,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
|> publish_one()
|
||||
end
|
||||
|
||||
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
|
||||
if port == URI.default_port(scheme) do
|
||||
host
|
||||
else
|
||||
"#{host}:#{port}"
|
||||
end
|
||||
end
|
||||
defp format_error_response(%Tesla.Env{status: code, headers: headers}),
|
||||
do: {:http_error, code, headers}
|
||||
|
||||
defp format_error_response(%Tesla.Env{} = env),
|
||||
do: {:http_error, :connect, Pleroma.HTTP.Middleware.HTTPSignature.redact_keys(env)}
|
||||
|
||||
defp format_error_response(response), do: response
|
||||
|
||||
defp blocked_instances do
|
||||
Config.get([:instance, :quarantined_instances], []) ++
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
media_proxy_url = MediaProxy.url(url)
|
||||
|
||||
with {:ok, %{status: status} = head_response} when status in 200..299 <-
|
||||
Pleroma.HTTP.request("HEAD", media_proxy_url, [], [], name: MyFinch) do
|
||||
Pleroma.HTTP.head(media_proxy_url) do
|
||||
content_type = Tesla.get_header(head_response, "content-type")
|
||||
content_length = Tesla.get_header(head_response, "content-length")
|
||||
content_length = content_length && String.to_integer(content_length)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.RelMe do
|
||||
@options [
|
||||
max_body: 2_000_000,
|
||||
receive_timeout: 2_000
|
||||
adapter: [receive_timeout: 2_000]
|
||||
]
|
||||
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
|
||||
|
||||
head_check =
|
||||
case Pleroma.HTTP.head(url, headers, http_options()) do
|
||||
case Pleroma.HTTP.head(url, headers) do
|
||||
# If the HEAD request didn't reach the server for whatever reason,
|
||||
# we assume the GET that comes right after won't either
|
||||
{:error, _} = e ->
|
||||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
:ok
|
||||
end
|
||||
|
||||
with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, http_options())
|
||||
with :ok <- head_check, do: Pleroma.HTTP.get(url, headers)
|
||||
end
|
||||
|
||||
defp check_content_type(headers) do
|
||||
|
|
@ -41,7 +41,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
end
|
||||
|
||||
defp check_content_length(headers) do
|
||||
max_body = Keyword.get(http_options(), :max_body)
|
||||
max_body = Config.get([:rich_media, :max_body], 5_000_000)
|
||||
|
||||
case List.keyfind(headers, "content-length", 0) do
|
||||
{_, maybe_content_length} ->
|
||||
|
|
@ -55,11 +55,4 @@ defmodule Pleroma.Web.RichMedia.Helpers do
|
|||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp http_options do
|
||||
[
|
||||
pool: :media,
|
||||
max_body: Config.get([:rich_media, :max_body], 5_000_000)
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ defmodule Pleroma.Workers.PublisherWorker do
|
|||
# instance / actor was explicitly deleted; there’s nothing to deliver to anymore
|
||||
# since we don’t know whether the whole instance is gone or just this actor,
|
||||
# do NOT immediately mark the instance as unreachable
|
||||
{:error, %{status: 410}} ->
|
||||
{:error, {:http_error, 410, _}} ->
|
||||
:ok
|
||||
|
||||
res ->
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.HTTP.RequestBuilderTest do
|
||||
use ExUnit.Case
|
||||
use Pleroma.Tests.Helpers
|
||||
alias Pleroma.HTTP.Request
|
||||
alias Pleroma.HTTP.RequestBuilder
|
||||
|
||||
describe "headers/2" do
|
||||
test "don't send pleroma user agent" do
|
||||
assert RequestBuilder.headers(%Request{}, []) == %Request{headers: []}
|
||||
end
|
||||
|
||||
test "send pleroma user agent" do
|
||||
clear_config([:http, :send_user_agent], true)
|
||||
clear_config([:http, :user_agent], :default)
|
||||
|
||||
assert RequestBuilder.headers(%Request{}, []) == %Request{
|
||||
headers: [{"user-agent", Pleroma.Application.user_agent()}]
|
||||
}
|
||||
end
|
||||
|
||||
test "send custom user agent" do
|
||||
clear_config([:http, :send_user_agent], true)
|
||||
clear_config([:http, :user_agent], "totally-not-pleroma")
|
||||
|
||||
assert RequestBuilder.headers(%Request{}, []) == %Request{
|
||||
headers: [{"user-agent", "totally-not-pleroma"}]
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe "add_param/4" do
|
||||
test "add file parameter" do
|
||||
assert match?(
|
||||
%Request{
|
||||
body: %Tesla.Multipart{
|
||||
boundary: _,
|
||||
content_type_params: [],
|
||||
parts: [
|
||||
%Tesla.Multipart.Part{
|
||||
body: %File.Stream{
|
||||
line_or_bytes: 2048,
|
||||
modes: [:raw, :read_ahead, :binary],
|
||||
path: "some-path/filename.png",
|
||||
raw: true
|
||||
},
|
||||
dispositions: [name: "filename.png", filename: "filename.png"],
|
||||
headers: []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
RequestBuilder.add_param(
|
||||
%Request{},
|
||||
:file,
|
||||
"filename.png",
|
||||
"some-path/filename.png"
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
test "add key to body" do
|
||||
%{
|
||||
body: %Tesla.Multipart{
|
||||
boundary: _,
|
||||
content_type_params: [],
|
||||
parts: [
|
||||
%Tesla.Multipart.Part{
|
||||
body: "\"someval\"",
|
||||
dispositions: [name: "somekey"],
|
||||
headers: [{"content-type", "application/json"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
} = RequestBuilder.add_param(%{}, :body, "somekey", "someval")
|
||||
end
|
||||
|
||||
test "add form parameter" do
|
||||
assert RequestBuilder.add_param(%{}, :form, "somename", "someval") == %{
|
||||
body: %{"somename" => "someval"}
|
||||
}
|
||||
end
|
||||
|
||||
test "add for location" do
|
||||
assert RequestBuilder.add_param(%{}, :some_location, "somekey", "someval") == %{
|
||||
some_location: [{"somekey", "someval"}]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -478,7 +478,7 @@ defmodule Pleroma.Object.FetcherTest do
|
|||
|
||||
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
assert called(Pleroma.Signature.sign(:_, :_))
|
||||
assert called(Pleroma.Signature.sign(:_, :_, :_))
|
||||
end
|
||||
|
||||
test_with_mock "it doesn't sign fetches when not configured to do so",
|
||||
|
|
@ -489,7 +489,7 @@ defmodule Pleroma.Object.FetcherTest do
|
|||
|
||||
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
|
||||
|
||||
refute called(Pleroma.Signature.sign(:_, :_))
|
||||
refute called(Pleroma.Signature.sign(:_, :_, :_))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ defmodule Pleroma.SignatureTest do
|
|||
|
||||
import Pleroma.Factory
|
||||
import Tesla.Mock
|
||||
import Mock
|
||||
|
||||
alias HTTPSignatures.HTTPKey
|
||||
alias Pleroma.Signature
|
||||
|
|
@ -130,24 +129,11 @@ defmodule Pleroma.SignatureTest do
|
|||
|
||||
assert_signature_equal(
|
||||
Signature.sign(
|
||||
user,
|
||||
user.signing_key,
|
||||
headers
|
||||
),
|
||||
~s|keyId="https://mastodon.social/users/lambadalambda#main-key",algorithm="rsa-sha256",headers="(request-target) content-length date digest host",signature="fhOT6IBThnCo6rv2Tv8BRXLV7LvVf/7wTX/bbPLtdq5A4GUqrmXUcY5p77jQ6NU9IRIVczeeStxQV6TrHqk/qPdqQOzDcB6cWsSfrB1gsTinBbAWdPzQYqUOTl+Minqn2RERAfPebKYr9QGa0sTODDHvze/UFPuL8a1lDO2VQE0lRCdg49Igr8pGl/CupUx8Fb874omqP0ba3M+siuKEwo02m9hHcbZUeLSN0ZVdvyTMttyqPM1BfwnFXkaQRAblLTyzt4Fv2+fTN+zPipSxJl1YIo1TsmwNq9klqImpjh8NHM3MJ5eZxTZ109S6Q910n1Lm46V/SqByDaYeg9g7Jw=="|
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "signed_date" do
|
||||
test "it returns formatted current date" do
|
||||
with_mock(NaiveDateTime, utc_now: fn -> ~N[2019-08-23 18:11:24.822233] end) do
|
||||
assert Signature.signed_date() == "Fri, 23 Aug 2019 18:11:24 GMT"
|
||||
end
|
||||
end
|
||||
|
||||
test "it returns formatted date" do
|
||||
assert Signature.signed_date(~N[2019-08-23 08:11:24.822233]) ==
|
||||
"Fri, 23 Aug 2019 08:11:24 GMT"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
media_proxy_url: media_proxy_url
|
||||
} do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 500, body: ""}
|
||||
end)
|
||||
|
||||
|
|
@ -178,7 +178,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
media_proxy_url: media_proxy_url
|
||||
} do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "application/pdf"}]}
|
||||
end)
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
clear_config([:media_preview_proxy, :min_content_length], 1_000_000_000)
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: "",
|
||||
|
|
@ -223,7 +223,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
media_proxy_url: media_proxy_url
|
||||
} do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/gif"}]}
|
||||
end)
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
media_proxy_url: media_proxy_url
|
||||
} do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
|
||||
end)
|
||||
|
||||
|
|
@ -261,7 +261,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
clear_config([:media_preview_proxy, :min_content_length], 100_000)
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: "",
|
||||
|
|
@ -283,7 +283,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
assert_dependencies_installed()
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/png"}]}
|
||||
|
||||
%{method: :get, url: ^media_proxy_url} ->
|
||||
|
|
@ -305,7 +305,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
assert_dependencies_installed()
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
|
||||
|
||||
%{method: :get, url: ^media_proxy_url} ->
|
||||
|
|
@ -325,7 +325,7 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do
|
|||
media_proxy_url: media_proxy_url
|
||||
} do
|
||||
Tesla.Mock.mock(fn
|
||||
%{method: "HEAD", url: ^media_proxy_url} ->
|
||||
%{method: :head, url: ^media_proxy_url} ->
|
||||
%Tesla.Env{status: 200, body: "", headers: [{"content-type", "image/jpeg"}]}
|
||||
|
||||
%{method: :get, url: ^media_proxy_url} ->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue