Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85803488
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/http.ex b/lib/pleroma/http.ex
index 5ac7f96f9..0d9f28420 100644
--- a/lib/pleroma/http.ex
+++ b/lib/pleroma/http.ex
@@ -1,244 +1,245 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP do
@moduledoc """
Wrapper for `Tesla.request/2`.
"""
alias Pleroma.HTTP.AdapterHelper
alias Pleroma.HTTP.Request
alias Pleroma.HTTP.RequestBuilder, as: Builder
alias Tesla.Client
alias Tesla.Env
require Logger
require Pleroma.Constants
@type t :: __MODULE__
@type method() :: :get | :post | :put | :delete | :head
@doc """
Performs GET request.
See `Pleroma.HTTP.request/5`
"""
@spec get(Request.url() | nil, Request.headers(), keyword()) ::
nil | {:ok, Env.t()} | {:error, any()}
def get(url, headers \\ [], options \\ [])
def get(nil, _, _), do: nil
def get(url, headers, options), do: request(:get, url, "", headers, options)
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
@doc """
Performs POST request.
See `Pleroma.HTTP.request/5`
"""
@spec post(Request.url(), Tesla.Env.body(), Request.headers(), keyword()) ::
{:ok, Env.t()} | {:error, any()}
def post(url, body, headers \\ [], options \\ []),
do: request(:post, url, body, headers, options)
@doc """
Builds and performs http request.
# Arguments:
`method` - :get, :post, :put, :delete, :head
`url` - full url
`body` - request body
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
`options` - custom, per-request middleware or adapter options
# Returns:
`{:ok, %Tesla.Env{}}` or `{:error, error}`
"""
@spec request(method(), Request.url(), Tesla.Env.body(), Request.headers(), keyword()) ::
{: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(uri, options || [])
options = put_in(options[:adapter], adapter_opts)
params = options[:params] || []
request = build_request(method, headers, options, url, body, params)
adapter = Application.get_env(:tesla, :adapter)
extra_middleware = options[:tesla_middleware] || []
client = Tesla.client(adapter_middlewares(adapter, extra_middleware), adapter)
maybe_limit(
fn ->
request(client, request)
end,
adapter,
adapter_opts
)
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()
end
@prefix Pleroma.Gun.ConnectionPool
defp maybe_limit(fun, Tesla.Adapter.Gun, opts) do
ConcurrentLimiter.limit(:"#{@prefix}.#{opts[:pool] || :default}", fun)
end
defp maybe_limit(fun, _, _) do
fun.()
end
defp adapter_middlewares(Tesla.Adapter.Gun, extra_middleware) do
default_middleware() ++
[Pleroma.Tesla.Middleware.ConnectionPool] ++
extra_middleware
end
defp adapter_middlewares(_, extra_middleware) do
# A lot of tests are written expecting unencoded URLs
# and the burden of fixing that is high. Also it makes
# them hard to read. Tests will opt-in when we want to validate
# the encoding is being done correctly.
cond do
Pleroma.Config.get(:env) == :test and Pleroma.Config.get(:test_url_encoding) ->
default_middleware()
Pleroma.Config.get(:env) == :test ->
# Emulate redirects in test env, which are handled by adapters in other environments
[Tesla.Middleware.FollowRedirects]
# Hackney and Finch
true ->
default_middleware() ++ extra_middleware
end
end
defp default_middleware,
do: [Tesla.Middleware.FollowRedirects, Pleroma.Tesla.Middleware.EncodeUrl]
# 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)
|> maybe_apply_path_encoding_quirks()
query = encode_query(parsed.query)
%{parsed | path: path, query: query}
end)
|> URI.to_string()
end
end
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, bypass_decode) when is_binary(path) do
path =
cond do
bypass_decode ->
path
true ->
URI.decode(path)
end
path
|> URI.encode(fn byte ->
URI.char_unreserved?(byte) ||
Enum.any?(
Pleroma.Constants.uri_path_allowed_reserved_chars(),
fn char ->
char == byte
end
)
end)
end
defp encode_query(nil), do: nil
# Order of kv pairs in query is not preserved when using URI.decode_query.
# URI.query_decoder/2 returns a stream which so far appears to not change order.
# Immediately switch to a list to prevent breakage for sites that expect
# the order of query keys to be always the same.
defp encode_query(query) when is_binary(query) do
query
|> URI.query_decoder()
|> Enum.to_list()
|> do_encode_query()
end
defp maybe_apply_path_encoding_quirks(path), do: path
# Always uses www_form encoding
defp do_encode_query(enumerable) do
Enum.map_join(enumerable, "&", &maybe_apply_query_quirk(&1))
end
defp maybe_apply_query_quirk({key, value}) do
case key do
"precrop" ->
query_encode_kv_pair({key, value}, ~c":,")
key ->
query_encode_kv_pair({key, value})
end
end
defp query_encode_kv_pair({key, value}, rules \\ []) when is_list(rules) do
cond do
length(rules) > 0 ->
- # URI.encode_query/2 does not appear to follow spec and encodes all parts of our URI path Constant.
- # This appears to work outside of edge-cases like The Guardian Rich Media Cards,
- # keeping behavior same as with URI.encode_query/2 unless otherwise specified via rules.
+ # URI.encode_query/2 does not appear to follow spec and encodes all part
+ # of our URI path Constant. This appears to work outside of edge-cases
+ # like The Guardian Rich Media Cards, keeping behavior same as with
+ # URI.encode_query/2 unless otherwise specified via rules.
(URI.encode_www_form(Kernel.to_string(key)) <>
"=" <>
URI.encode(value, fn byte ->
URI.char_unreserved?(byte) ||
Enum.any?(
rules,
fn char ->
char == byte
end
)
end))
|> String.replace("%20", "+")
true ->
URI.encode_www_form(Kernel.to_string(key)) <>
"=" <> URI.encode_www_form(Kernel.to_string(value))
end
end
end
diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index a29debbfc..22f37eea8 100644
--- a/lib/pleroma/web/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -1,193 +1,194 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MediaProxy do
alias Pleroma.Config
alias Pleroma.Helpers.UriHelper
alias Pleroma.HTTP
alias Pleroma.Upload
alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy.Invalidation
@base64_opts [padding: false]
@cache_table :banned_urls_cache
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
def cache_table, do: @cache_table
@spec in_banned_urls(String.t()) :: boolean()
def in_banned_urls(url), do: elem(@cachex.exists?(@cache_table, url(url)), 1)
def remove_from_banned_urls(urls) when is_list(urls) do
@cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &@cachex.del(cache, &1))
end)
end
def remove_from_banned_urls(url) when is_binary(url) do
@cachex.del(@cache_table, url(url))
end
def put_in_banned_urls(urls) when is_list(urls) do
@cachex.execute!(@cache_table, fn cache ->
Enum.each(Invalidation.prepare_urls(urls), &@cachex.put(cache, &1, true))
end)
end
def put_in_banned_urls(url) when is_binary(url) do
@cachex.put(@cache_table, url(url), true)
end
def url(url) when is_nil(url) or url == "", do: nil
def url("/" <> _ = url), do: url
def url(url) do
if enabled?() and url_proxiable?(url) do
encode_url(url)
else
url
end
end
@spec url_proxiable?(String.t()) :: boolean()
def url_proxiable?(url) do
not local?(url) and not whitelisted?(url)
end
def preview_url(url, preview_params \\ []) do
if preview_enabled?() do
encode_preview_url(url, preview_params)
else
url(url)
end
end
def enabled?, do: Config.get([:media_proxy, :enabled], false)
# Note: media proxy must be enabled for media preview proxy in order to load all
# non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled])
def local?(url), do: String.starts_with?(url, Endpoint.url())
def whitelisted?(url) do
%{host: domain} = URI.parse(url)
mediaproxy_whitelist_domains =
Config.get([:media_proxy, :whitelist], [])
|> Kernel.++(["#{Upload.base_url()}"])
|> Enum.map(&maybe_get_domain_from_url/1)
domain in mediaproxy_whitelist_domains
end
defp maybe_get_domain_from_url("http" <> _ = url) do
URI.parse(url).host
end
defp maybe_get_domain_from_url(domain), do: domain
defp base64_sig64(url) do
base64 = Base.url_encode64(url, @base64_opts)
sig64 =
base64
|> signed_url()
|> Base.url_encode64(@base64_opts)
{base64, sig64}
end
# The URL coming into MediaProxy from the outside might have wrong %-encoding
- # (like older Pleroma versions)
+ # (like older Pleroma versions).
# This would cause an inconsistency with the encoded URL here and the requested
# URL fixed with Pleroma.Tesla.Middleware.EncodeUrl.
- # End result is a failing HEAD request in Pleroma.Web.MediaProxy.MediaProxyController.handle_preview/2
+ # End result is a failing HEAD request in
+ # Pleroma.Web.MediaProxy.MediaProxyController.handle_preview/2
def encode_url(url) do
url = HTTP.encode_url(url)
{base64, sig64} = base64_sig64(url)
build_url(sig64, base64, filename(url))
end
def encode_preview_url(url, preview_params \\ []) do
url = HTTP.encode_url(url)
{base64, sig64} = base64_sig64(url)
build_preview_url(sig64, base64, filename(url), preview_params)
end
def decode_url(sig, url) do
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
signature when signature == sig <- signed_url(url) do
{:ok, Base.url_decode64!(url, @base64_opts)}
else
_ -> {:error, :invalid_signature}
end
end
def decode_url(encoded) do
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
decode_url(sig, base64)
end
defp signed_url(url) do
:crypto.mac(:hmac, :sha, Config.get([Endpoint, :secret_key_base]), url)
end
def filename(url_or_path) do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end
def base_url do
Config.get([:media_proxy, :base_url], Endpoint.url())
end
defp proxy_url(path, sig_base64, url_base64, filename) do
[
base_url(),
path,
sig_base64,
url_base64,
filename
]
|> Enum.filter(& &1)
|> Path.join()
end
def build_url(sig_base64, url_base64, filename \\ nil) do
proxy_url("proxy", sig_base64, url_base64, filename)
end
def build_preview_url(sig_base64, url_base64, filename \\ nil, preview_params \\ []) do
uri = proxy_url("proxy/preview", sig_base64, url_base64, filename)
UriHelper.modify_uri_params(uri, preview_params)
end
def verify_request_path_and_url(
%Plug.Conn{params: %{"filename" => _}, request_path: request_path},
url
) do
verify_request_path_and_url(request_path, url)
end
def verify_request_path_and_url(request_path, url) when is_binary(request_path) do
filename = filename(url)
if filename && not basename_matches?(request_path, filename) do
{:wrong_filename, filename}
else
:ok
end
end
def verify_request_path_and_url(_, _), do: :ok
defp basename_matches?(path, filename) do
basename = Path.basename(path)
basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename
end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Oct 9, 8:09 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784547
Default Alt Text
(13 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment