# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy do
+ @range_headers ~w(range if-range)
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
- ~w(if-unmodified-since if-none-match if-range range)
- @resp_cache_headers ~w(etag date last-modified cache-control)
+ ~w(if-unmodified-since if-none-match) ++ @range_headers
+ @resp_cache_headers ~w(etag date last-modified)
@keep_resp_headers @resp_cache_headers ++
- ~w(content-type content-disposition content-encoding content-range) ++
- ~w(accept-ranges vary)
+ ~w(content-length content-type content-disposition content-encoding) ++
+ ~w(content-range accept-ranges vary)
@default_cache_control_header "public, max-age=1209600"
@valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30)
* request: `#{inspect(@keep_req_headers)}`
* response: `#{inspect(@keep_resp_headers)}`
- If no caching headers (`#{inspect(@resp_cache_headers)}`) are returned by upstream, `cache-control` will be
- set to `#{inspect(@default_cache_control_header)}`.
-
Options:
* `redirect_on_failure` (default `false`). Redirects the client to the real remote URL if there's any HTTP
end
defp response(conn, client, url, status, headers, opts) do
+ Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}")
+
result =
conn
|> put_resp_headers(build_resp_headers(headers, opts))
end
end
- defp head_response(conn, _url, code, headers, opts) do
+ defp head_response(conn, url, code, headers, opts) do
+ Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}")
+
conn
|> put_resp_headers(build_resp_headers(headers, opts))
|> send_resp(code, "")
headers
|> downcase_headers()
|> Enum.filter(fn {k, _} -> k in @keep_req_headers end)
- |> (fn headers ->
- headers = headers ++ Keyword.get(opts, :req_headers, [])
-
- if Keyword.get(opts, :keep_user_agent, false) do
- List.keystore(
- headers,
- "user-agent",
- 0,
- {"user-agent", Pleroma.Application.user_agent()}
- )
- else
- headers
- end
- end).()
+ |> build_req_range_or_encoding_header(opts)
+ |> build_req_user_agent_header(opts)
+ |> Keyword.merge(Keyword.get(opts, :req_headers, []))
+ end
+
+ # Disable content-encoding if any @range_headers are requested (see #1823).
+ defp build_req_range_or_encoding_header(headers, _opts) do
+ range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end)
+
+ if range? && List.keymember?(headers, "accept-encoding", 0) do
+ List.keydelete(headers, "accept-encoding", 0)
+ else
+ headers
+ end
+ end
+
+ defp build_req_user_agent_header(headers, opts) do
+ if Keyword.get(opts, :keep_user_agent, false) do
+ List.keystore(
+ headers,
+ "user-agent",
+ 0,
+ {"user-agent", Pleroma.Application.user_agent()}
+ )
+ else
+ headers
+ end
end
defp build_resp_headers(headers, opts) do
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|> build_resp_cache_headers(opts)
|> build_resp_content_disposition_header(opts)
- |> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).()
+ |> Keyword.merge(Keyword.get(opts, :resp_headers, []))
end
defp build_resp_cache_headers(headers, _opts) do
has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end)
- has_cache_control? = List.keymember?(headers, "cache-control", 0)
cond do
- has_cache? && has_cache_control? ->
- headers
-
has_cache? ->
- # There's caching header present but no cache-control -- we need to explicitely override it
- # to public as Plug defaults to "max-age=0, private, must-revalidate"
- List.keystore(headers, "cache-control", 0, {"cache-control", "public"})
+ # There's caching header present but no cache-control -- we need to set our own
+ # as Plug defaults to "max-age=0, private, must-revalidate"
+ List.keystore(
+ headers,
+ "cache-control",
+ 0,
+ {"cache-control", @default_cache_control_header}
+ )
true ->
List.keystore(