commit: 11d27349e32d23649dd4e5ba6a3597f62199e6e5
parent 1d8eafc0d2c38fdcac34af6b71291e9c8833a20b
Author: Mark Felder <feld@feld.me>
Date: Wed, 30 Jul 2025 11:05:49 -0700
Fix HTTP client making invalid requests due to no percent encoding processing or validation.
Diffstat:
3 files changed, 61 insertions(+), 3 deletions(-)
diff --git a/changelog.d/url-encoding.fix b/changelog.d/url-encoding.fix
@@ -0,0 +1 @@
+Fix HTTP client making invalid requests due to no percent encoding processing or validation.
diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex
@@ -105,20 +105,24 @@ defmodule Pleroma.HTTP do
end
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do
- [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.ConnectionPool] ++
+ default_middleware() ++
+ [Pleroma.Tesla.Middleware.ConnectionPool] ++
extra_middleware
end
defp adapter_middlewares({Tesla.Adapter.Finch, _}, extra_middleware) do
- [Tesla.Middleware.FollowRedirects] ++ extra_middleware
+ default_middleware() ++ extra_middleware
end
defp adapter_middlewares(_, extra_middleware) do
if Pleroma.Config.get(:env) == :test do
# Emulate redirects in test env, which are handled by adapters in other environments
- [Tesla.Middleware.FollowRedirects]
+ default_middleware()
else
extra_middleware
end
end
+
+ defp default_middleware(),
+ do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl]
end
diff --git a/lib/pleroma/tesla/middleware/encode_url.ex b/lib/pleroma/tesla/middleware/encode_url.ex
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2025 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Tesla.Middleware.EncodeUrl do
+ @moduledoc """
+ Middleware to encode URLs properly
+
+ We must decode and then re-encode to ensure correct encoding.
+ If we only encode it will re-encode each % as %25 causing a space
+ already encoded as %20 to be %2520.
+
+ Similar problem for query parameters which need spaces to be the + character
+ """
+
+ @behaviour Tesla.Middleware
+
+ @impl Tesla.Middleware
+ def call(%Tesla.Env{url: url} = env, next, _) do
+ url =
+ URI.parse(url)
+ |> then(fn parsed ->
+ path = encode_path(parsed.path)
+ query = encode_query(parsed.query)
+
+ %{parsed | path: path, query: query}
+ end)
+ |> URI.to_string()
+
+ env = %{env | url: url}
+
+ case Tesla.run(env, next) do
+ {:ok, env} -> {:ok, env}
+ err -> err
+ end
+ end
+
+ defp encode_path(nil), do: nil
+
+ defp encode_path(path) when is_binary(path) do
+ path
+ |> URI.decode()
+ |> URI.encode()
+ end
+
+ defp encode_query(nil), do: nil
+
+ defp encode_query(query) when is_binary(query) do
+ query
+ |> URI.decode_query()
+ |> URI.encode_query()
+ end
+end