logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: 0a8423fdf726e6c1d9d6bf781e14d610bb917ed9
parent 619f247e38eb70746426d6440a373c4d682c776b
Author: Phantasm <phantasm@centrum.cz>
Date:   Fri, 29 Aug 2025 14:47:19 +0200

Add ability to bypass url decode/parse in Pleroma.HTTP, fix encode in Pleroma.Upload

Diffstat:

Mlib/pleroma/http.ex45++++++++++++++++++++++++++++++++-------------
Mlib/pleroma/upload.ex11+++++++++--
Mtest/pleroma/upload_test.exs15+++++++++++++++
3 files changed, 56 insertions(+), 15 deletions(-)

diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex @@ -133,32 +133,51 @@ defmodule Pleroma.HTTP do defp default_middleware, do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl] - def encode_url(url) when is_binary(url) do - 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() + # We don't always want to decode the path first, like is the case in + # Pleroma.Upload.url_from_spec/3. + def encode_url(url, opts \\ []) when is_binary(url) and is_list(opts) do + bypass_parse = Keyword.get(opts, :bypass_parse, false) + bypass_decode = Keyword.get(opts, :bypass_decode, false) + + cond do + bypass_parse -> + encode_path(url, bypass_decode) + + true -> + URI.parse(url) + |> then(fn parsed -> + path = encode_path(parsed.path, bypass_decode) + query = encode_query(parsed.query) + + %{parsed | path: path, query: query} + end) + |> URI.to_string() + end end - defp encode_path(nil), do: nil + defp encode_path(nil, _bypass_decode), do: nil # URI.encode/2 deliberately does not encode all chars that are forbidden # in the path component of a URI. It only encodes chars that are forbidden # in the whole URI. A predicate in the 2nd argument is used to fix that here. # URI.encode/2 uses the predicate function to determine whether each byte # (in an integer representation) should be encoded or not. - defp encode_path(path) when is_binary(path) do + defp encode_path(path, bypass_decode) when is_binary(path) do + path = + cond do + bypass_decode -> + path + + true -> + URI.decode(path) + end + path - |> URI.decode() |> URI.encode(fn byte -> URI.char_unreserved?(byte) || Enum.any?( Pleroma.Constants.uri_path_allowed_reserved_chars, fn char -> char == byte end) - end) + end) end defp encode_query(nil), do: nil diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex @@ -34,6 +34,7 @@ defmodule Pleroma.Upload do """ alias Ecto.UUID + alias Pleroma.HTTP alias Pleroma.Maps alias Pleroma.Web.ActivityPub.Utils require Logger @@ -230,11 +231,17 @@ defmodule Pleroma.Upload do tmp_path end + # Encoding the whole path here is fine since the path is in a + # UUID/<file name> form. + # The file at this point isn't %-encoded, so the path shouldn't + # be decoded first like Pleroma.HTTP.encode_url/1 does. defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do + encode_opts = [bypass_decode: true, bypass_parse: true] + path = - URI.encode(path, &char_unescaped?/1) <> + HTTP.encode_url(path, encode_opts) <> if Pleroma.Config.get([__MODULE__, :link_name], false) do - "?name=#{URI.encode(name, &char_unescaped?/1)}" + "?name=#{URI.encode_query(name)}" else "" end diff --git a/test/pleroma/upload_test.exs b/test/pleroma/upload_test.exs @@ -242,6 +242,21 @@ defmodule Pleroma.UploadTest do assert Path.basename(attachment_url["href"]) == "%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg" end + + test "double %-encodes filename" do + File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") + + file = %Plug.Upload{ + content_type: "image/jpeg", + path: Path.absname("test/fixtures/image_tmp.jpg"), + filename: "file with %20.jpg" + } + + {:ok, data} = Upload.store(file) + [attachment_url | _] = data["url"] + + assert Path.basename(attachment_url["href"]) == "file%20with%20%2520.jpg" + end end describe "Setting a custom base_url for uploaded media" do