[#2497] Fixed merge issue.
[akkoma] / lib / pleroma / web / media_proxy / media_proxy.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MediaProxy do
6 alias Pleroma.Config
7 alias Pleroma.Upload
8 alias Pleroma.Web
9 alias Pleroma.Web.MediaProxy.Invalidation
10
11 @base64_opts [padding: false]
12
13 @spec in_banned_urls(String.t()) :: boolean()
14 def in_banned_urls(url), do: elem(Cachex.exists?(:banned_urls_cache, url(url)), 1)
15
16 def remove_from_banned_urls(urls) when is_list(urls) do
17 Cachex.execute!(:banned_urls_cache, fn cache ->
18 Enum.each(Invalidation.prepare_urls(urls), &Cachex.del(cache, &1))
19 end)
20 end
21
22 def remove_from_banned_urls(url) when is_binary(url) do
23 Cachex.del(:banned_urls_cache, url(url))
24 end
25
26 def put_in_banned_urls(urls) when is_list(urls) do
27 Cachex.execute!(:banned_urls_cache, fn cache ->
28 Enum.each(Invalidation.prepare_urls(urls), &Cachex.put(cache, &1, true))
29 end)
30 end
31
32 def put_in_banned_urls(url) when is_binary(url) do
33 Cachex.put(:banned_urls_cache, url(url), true)
34 end
35
36 def url(url) when is_nil(url) or url == "", do: nil
37 def url("/" <> _ = url), do: url
38
39 def url(url) do
40 if not enabled?() or not url_proxiable?(url) do
41 url
42 else
43 encode_url(url)
44 end
45 end
46
47 @spec url_proxiable?(String.t()) :: boolean()
48 def url_proxiable?(url) do
49 if local?(url) or whitelisted?(url) do
50 false
51 else
52 true
53 end
54 end
55
56 # Note: routing all URLs to preview handler (even local and whitelisted).
57 # Preview handler will call url/1 on decoded URLs, and applicable ones will detour media proxy.
58 def preview_url(url) do
59 if preview_enabled?() do
60 encode_preview_url(url)
61 else
62 url
63 end
64 end
65
66 def enabled?, do: Config.get([:media_proxy, :enabled], false)
67
68 # Note: media proxy must be enabled for media preview proxy in order to load all
69 # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
70 def preview_enabled?, do: enabled?() and Config.get([:media_preview_proxy, :enabled], false)
71
72 def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
73
74 def whitelisted?(url) do
75 %{host: domain} = URI.parse(url)
76
77 mediaproxy_whitelist = Config.get([:media_proxy, :whitelist])
78
79 upload_base_url_domain =
80 if !is_nil(Config.get([Upload, :base_url])) do
81 [URI.parse(Config.get([Upload, :base_url])).host]
82 else
83 []
84 end
85
86 whitelist = mediaproxy_whitelist ++ upload_base_url_domain
87
88 Enum.any?(whitelist, fn pattern ->
89 String.equivalent?(domain, pattern)
90 end)
91 end
92
93 defp base64_sig64(url) do
94 base64 = Base.url_encode64(url, @base64_opts)
95
96 sig64 =
97 base64
98 |> signed_url()
99 |> Base.url_encode64(@base64_opts)
100
101 {base64, sig64}
102 end
103
104 def encode_url(url) do
105 {base64, sig64} = base64_sig64(url)
106
107 build_url(sig64, base64, filename(url))
108 end
109
110 def encode_preview_url(url) do
111 {base64, sig64} = base64_sig64(url)
112
113 build_preview_url(sig64, base64, filename(url))
114 end
115
116 def decode_url(sig, url) do
117 with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
118 signature when signature == sig <- signed_url(url) do
119 {:ok, Base.url_decode64!(url, @base64_opts)}
120 else
121 _ -> {:error, :invalid_signature}
122 end
123 end
124
125 defp signed_url(url) do
126 :crypto.hmac(:sha, Config.get([Web.Endpoint, :secret_key_base]), url)
127 end
128
129 def filename(url_or_path) do
130 if path = URI.parse(url_or_path).path, do: Path.basename(path)
131 end
132
133 defp proxy_url(path, sig_base64, url_base64, filename) do
134 [
135 Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
136 path,
137 sig_base64,
138 url_base64,
139 filename
140 ]
141 |> Enum.filter(& &1)
142 |> Path.join()
143 end
144
145 def build_url(sig_base64, url_base64, filename \\ nil) do
146 proxy_url("proxy", sig_base64, url_base64, filename)
147 end
148
149 def build_preview_url(sig_base64, url_base64, filename \\ nil) do
150 proxy_url("proxy/preview", sig_base64, url_base64, filename)
151 end
152
153 def verify_request_path_and_url(
154 %Plug.Conn{params: %{"filename" => _}, request_path: request_path},
155 url
156 ) do
157 verify_request_path_and_url(request_path, url)
158 end
159
160 def verify_request_path_and_url(request_path, url) when is_binary(request_path) do
161 filename = filename(url)
162
163 if filename && not basename_matches?(request_path, filename) do
164 {:wrong_filename, filename}
165 else
166 :ok
167 end
168 end
169
170 def verify_request_path_and_url(_, _), do: :ok
171
172 defp basename_matches?(path, filename) do
173 basename = Path.basename(path)
174 basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename
175 end
176 end