Page MenuHomePhorge

No OneTemporary

Size
8 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex
index 7ccd28bb1..7e1fca80d 100644
--- a/lib/pleroma/reverse_proxy/client/hackney.ex
+++ b/lib/pleroma/reverse_proxy/client/hackney.ex
@@ -1,71 +1,74 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy.Client.Hackney do
@behaviour Pleroma.ReverseProxy.Client
- # redirect handler from Pleb, slightly modified to work with Hackney
+ # In-app redirect handler to avoid Hackney redirect bugs:
+ # - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney)
+ # - https://github.com/benoitc/hackney/issues/273 (redirects not followed when using HTTP proxy)
+ #
+ # Based on a redirect handler from Pleb, slightly modified to work with Hackney:
# https://declin.eu/objects/d4f38e62-5429-4614-86d1-e8fc16e6bf33
- # https://github.com/benoitc/hackney/issues/273
@redirect_statuses [301, 302, 303, 307, 308]
defp absolute_redirect_url(original_url, resp_headers) do
location =
Enum.find(resp_headers, fn {header, _location} ->
String.downcase(header) == "location"
end)
URI.merge(original_url, elem(location, 1))
|> URI.to_string()
end
@impl true
def request(method, url, headers, body, opts \\ []) do
opts =
Keyword.put_new(opts, :path_encode_fun, fn path ->
path
end)
if opts[:follow_redirect] != false do
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
res = :hackney.request(method, url, headers, body, req_opts)
case res do
{:ok, code, resp_headers, _client} when code in @redirect_statuses ->
:hackney.request(
method,
absolute_redirect_url(url, resp_headers),
headers,
body,
req_opts
)
{:ok, code, resp_headers} when code in @redirect_statuses ->
:hackney.request(
method,
absolute_redirect_url(url, resp_headers),
headers,
body,
req_opts
)
_ ->
res
end
else
:hackney.request(method, url, headers, body, opts)
end
end
@impl true
def stream_body(ref) do
case :hackney.stream_body(ref) do
:done -> :done
{:ok, data} -> {:ok, data, ref}
{:error, error} -> {:error, error}
end
end
@impl true
def close(ref), do: :hackney.close(ref)
end
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index b0d07a6f8..43c2c0449 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -1,64 +1,71 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
@moduledoc "Preloads any attachments in the MediaProxy cache by prefetching them"
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
alias Pleroma.HTTP
alias Pleroma.Web.MediaProxy
require Logger
@impl true
def history_awareness, do: :auto
defp prefetch(url) do
# Fetching only proxiable resources
if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
# If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests)
prefetch_url = MediaProxy.preview_url(url)
Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
fetch(prefetch_url)
end
end
defp fetch(url) do
- http_client_opts = Pleroma.Config.get([:media_proxy, :proxy_opts, :http], pool: :media)
+ # This module uses Tesla (Pleroma.HTTP) to fetch the MediaProxy URL.
+ # Redirect following is handled by Tesla middleware, so we must not enable
+ # adapter-level redirect logic (Hackney can crash on relative redirects when proxied).
+ http_client_opts =
+ [:media_proxy, :proxy_opts, :http]
+ |> Pleroma.Config.get(pool: :media)
+ |> Keyword.drop([:follow_redirect, :force_redirect])
+
HTTP.get(url, [], http_client_opts)
end
defp preload(%{"object" => %{"attachment" => attachments}} = _activity) do
Enum.each(attachments, fn
%{"url" => url} when is_list(url) ->
url
|> Enum.each(fn
%{"href" => href} ->
prefetch(href)
x ->
Logger.debug("Unhandled attachment URL object #{inspect(x)}")
end)
x ->
Logger.debug("Unhandled attachment #{inspect(x)}")
end)
end
@impl true
def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = activity)
when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do
preload(activity)
{:ok, activity}
end
@impl true
def filter(activity), do: {:ok, activity}
@impl true
def describe, do: {:ok, %{}}
end
diff --git a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
index 0da3afa3b..4b94b9ac7 100644
--- a/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
@@ -1,106 +1,101 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do
use ExUnit.Case
use Pleroma.Tests.Helpers
alias Pleroma.HTTP
alias Pleroma.UnstubbedConfigMock, as: ConfigMock
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy
import Mock
import Mox
@message %{
"type" => "Create",
"object" => %{
"type" => "Note",
"content" => "content",
"attachment" => [
%{"url" => [%{"href" => "http://example.com/image.jpg"}]}
]
}
}
@message_with_history %{
"type" => "Create",
"object" => %{
"type" => "Note",
"content" => "content",
"formerRepresentations" => %{
"orderedItems" => [
%{
"type" => "Note",
"content" => "content",
"attachment" => [
%{"url" => [%{"href" => "http://example.com/image.jpg"}]}
]
}
]
}
}
}
setup do
ConfigMock
|> stub_with(Pleroma.Test.StaticConfig)
:ok
end
setup do: clear_config([:media_proxy, :enabled], true)
test "it prefetches media proxy URIs" do
- Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
- {:ok, %Tesla.Env{status: 200, body: ""}}
- end)
-
- with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
+ with_mock HTTP,
+ get: fn _, _, opts ->
+ send(self(), {:prefetch_opts, opts})
+ {:ok, []}
+ end do
MediaProxyWarmingPolicy.filter(@message)
assert called(HTTP.get(:_, :_, :_))
+ assert_receive {:prefetch_opts, opts}
+ refute Keyword.has_key?(opts, :follow_redirect)
+ refute Keyword.has_key?(opts, :force_redirect)
end
end
test "it does nothing when no attachments are present" do
object =
@message["object"]
|> Map.delete("attachment")
message =
@message
|> Map.put("object", object)
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
MediaProxyWarmingPolicy.filter(message)
refute called(HTTP.get(:_, :_, :_))
end
end
test "history-aware" do
- Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
- {:ok, %Tesla.Env{status: 200, body: ""}}
- end)
-
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history)
assert called(HTTP.get(:_, :_, :_))
end
end
test "works with Updates" do
- Tesla.Mock.mock(fn %{method: :get, url: "http://example.com/image.jpg"} ->
- {:ok, %Tesla.Env{status: 200, body: ""}}
- end)
-
with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do
MRF.filter_one(MediaProxyWarmingPolicy, @message_with_history |> Map.put("type", "Update"))
assert called(HTTP.get(:_, :_, :_))
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sun, Aug 30, 3:08 PM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1738046
Default Alt Text
(8 KB)

Event Timeline