StealEmoji: use Content-Type and reject non-images
E.g. *key’s emoji URLs typically don’t have file extensions, but until now we just slapped ".png" at its end hoping for the best. Furthermore, this gives us a chance to actually reject non-images, which before was not feasible exatly due to those extension-less URLs
This commit is contained in:
parent
111cdb0d86
commit
a8c6c780b4
2 changed files with 62 additions and 15 deletions
|
@ -33,16 +33,9 @@ defp reject_emoji?({shortcode, _url}, installed_emoji) do
|
|||
!valid_shortcode? or rejected_shortcode? or emoji_installed?
|
||||
end
|
||||
|
||||
defp steal_emoji(%{} = response, {shortcode, url}, emoji_dir_path) do
|
||||
extension =
|
||||
url
|
||||
|> URI.parse()
|
||||
|> Map.get(:path)
|
||||
|> Path.basename()
|
||||
|> Path.extname()
|
||||
|
||||
defp steal_emoji(%{} = response, {shortcode, extension}, emoji_dir_path) do
|
||||
shortcode = Path.basename(shortcode)
|
||||
file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png"))
|
||||
file_path = Path.join(emoji_dir_path, shortcode <> "." <> extension)
|
||||
|
||||
case File.write(file_path, response.body) do
|
||||
:ok ->
|
||||
|
@ -54,14 +47,25 @@ defp steal_emoji(%{} = response, {shortcode, url}, emoji_dir_path) do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_extension_if_safe(response) do
|
||||
content_type =
|
||||
:proplists.get_value("content-type", response.headers, MIME.from_path(response.url))
|
||||
|
||||
case content_type do
|
||||
"image/" <> _ -> List.first(MIME.extensions(content_type))
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do
|
||||
url = Pleroma.Web.MediaProxy.url(url)
|
||||
|
||||
with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do
|
||||
size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000)
|
||||
extension = get_extension_if_safe(response)
|
||||
|
||||
if byte_size(response.body) <= size_limit do
|
||||
steal_emoji(response, {shortcode, url}, emoji_dir_path)
|
||||
if byte_size(response.body) <= size_limit and extension do
|
||||
steal_emoji(response, {shortcode, extension}, emoji_dir_path)
|
||||
else
|
||||
Logger.debug(
|
||||
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
|
||||
|
|
|
@ -45,7 +45,11 @@ test "Steals emoji on unknown shortcode from allowed remote host", %{
|
|||
refute File.exists?(path)
|
||||
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} ->
|
||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/image.jpg"),
|
||||
url: "https://example.org/emoji/firedfox.png"
|
||||
}
|
||||
end)
|
||||
|
||||
clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
|
||||
|
@ -72,7 +76,11 @@ test "rejects invalid shortcodes", %{path: path} do
|
|||
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")}
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/image.jpg"),
|
||||
url: "https://example.org/emoji/firedfox.png"
|
||||
}
|
||||
end)
|
||||
|
||||
clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
|
||||
|
@ -86,6 +94,36 @@ test "rejects invalid shortcodes", %{path: path} do
|
|||
refute File.exists?(fullpath)
|
||||
end
|
||||
|
||||
test "prefers content-type header for extension", %{path: path} do
|
||||
message = %{
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"emoji" => [{"firedfox", "https://example.org/emoji/firedfox.fud"}],
|
||||
"actor" => "https://example.org/users/admin"
|
||||
}
|
||||
}
|
||||
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.fud"} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/image.jpg"),
|
||||
url: "https://example.org/emoji/firedfox.wevp",
|
||||
headers: [{"content-type", "image/gif"}]
|
||||
}
|
||||
end)
|
||||
|
||||
clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
|
||||
|
||||
assert {:ok, _message} = StealEmojiPolicy.filter(message)
|
||||
|
||||
assert "firedfox" in installed()
|
||||
assert File.exists?(path)
|
||||
|
||||
assert path
|
||||
|> Path.join("firedfox.gif")
|
||||
|> File.exists?()
|
||||
end
|
||||
|
||||
test "reject regex shortcode", %{message: message} do
|
||||
refute "firedfox" in installed()
|
||||
|
||||
|
@ -118,7 +156,11 @@ test "reject if size is above the limit", %{message: message} do
|
|||
refute "firedfox" in installed()
|
||||
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} ->
|
||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")}
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/image.jpg"),
|
||||
url: "https://example.org/emoji/firedfox.png"
|
||||
}
|
||||
end)
|
||||
|
||||
clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 50_000)
|
||||
|
@ -132,7 +174,8 @@ test "reject if host returns error", %{message: message} do
|
|||
refute "firedfox" in installed()
|
||||
|
||||
Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} ->
|
||||
{:ok, %Tesla.Env{status: 404, body: "Not found"}}
|
||||
{:ok,
|
||||
%Tesla.Env{status: 404, body: "Not found", url: "https://example.org/emoji/firedfox.png"}}
|
||||
end)
|
||||
|
||||
clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468)
|
||||
|
|
Loading…
Reference in a new issue