@@ -24,7 +24,6 @@ defmodule Pleroma.HTTP.AdapterHelper do | |||||
| {Connection.proxy_type(), Connection.host(), pos_integer()} | | {Connection.proxy_type(), Connection.host(), pos_integer()} | ||||
@callback options(keyword(), URI.t()) :: keyword() | @callback options(keyword(), URI.t()) :: keyword() | ||||
@callback after_request(keyword()) :: :ok | |||||
@callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()} | @callback get_conn(URI.t(), keyword()) :: {:ok, term()} | {:error, term()} | ||||
@spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil | @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil | ||||
@@ -67,9 +66,6 @@ defmodule Pleroma.HTTP.AdapterHelper do | |||||
Keyword.merge(opts, timeout: timeout) | Keyword.merge(opts, timeout: timeout) | ||||
end | end | ||||
@spec after_request(keyword()) :: :ok | |||||
def after_request(opts), do: adapter_helper().after_request(opts) | |||||
def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) | def get_conn(uri, opts), do: adapter_helper().get_conn(uri, opts) | ||||
defp adapter, do: Application.get_env(:tesla, :adapter) | defp adapter, do: Application.get_env(:tesla, :adapter) | ||||
@@ -9,9 +9,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Default do | |||||
AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy)) | AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy)) | ||||
end | end | ||||
@spec after_request(keyword()) :: :ok | |||||
def after_request(_opts), do: :ok | |||||
@spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | ||||
def get_conn(_uri, opts), do: {:ok, opts} | def get_conn(_uri, opts), do: {:ok, opts} | ||||
end | end |
@@ -34,15 +34,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do | |||||
|> Keyword.merge(incoming_opts) | |> Keyword.merge(incoming_opts) | ||||
end | end | ||||
@spec after_request(keyword()) :: :ok | |||||
def after_request(opts) do | |||||
if opts[:conn] && opts[:body_as] != :chunks do | |||||
ConnectionPool.release_conn(opts[:conn]) | |||||
end | |||||
:ok | |||||
end | |||||
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts | defp add_scheme_opts(opts, %{scheme: "http"}), do: opts | ||||
defp add_scheme_opts(opts, %{scheme: "https"}) do | defp add_scheme_opts(opts, %{scheme: "https"}) do | ||||
@@ -24,8 +24,6 @@ defmodule Pleroma.HTTP.AdapterHelper.Hackney do | |||||
defp add_scheme_opts(opts, _), do: opts | defp add_scheme_opts(opts, _), do: opts | ||||
def after_request(_), do: :ok | |||||
@spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()} | ||||
def get_conn(_uri, opts), do: {:ok, opts} | def get_conn(_uri, opts), do: {:ok, opts} | ||||
end | end |
@@ -69,14 +69,11 @@ defmodule Pleroma.HTTP do | |||||
request = build_request(method, headers, options, url, body, params) | request = build_request(method, headers, options, url, body, params) | ||||
adapter = Application.get_env(:tesla, :adapter) | adapter = Application.get_env(:tesla, :adapter) | ||||
client = Tesla.client([Tesla.Middleware.FollowRedirects], adapter) | |||||
client = Tesla.client([Pleroma.HTTP.Middleware.FollowRedirects], adapter) | |||||
response = request(client, request) | |||||
AdapterHelper.after_request(adapter_opts) | |||||
response | |||||
request(client, request) | |||||
# Connection release is handled in a custom FollowRedirects middleware | |||||
err -> | err -> | ||||
err | err | ||||
end | end | ||||
@@ -0,0 +1,106 @@ | |||||
# Pleroma: A lightweight social networking server | |||||
# Copyright © 2015-2020 Tymon Tobolski <https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex> | |||||
# Copyright © 2020 Pleroma Authors <https://pleroma.social/> | |||||
# SPDX-License-Identifier: AGPL-3.0-only | |||||
defmodule Pleroma.HTTP.Middleware.FollowRedirects do | |||||
@moduledoc """ | |||||
Pool-aware version of https://github.com/teamon/tesla/blob/master/lib/tesla/middleware/follow_redirects.ex | |||||
Follow 3xx redirects | |||||
## Options | |||||
- `:max_redirects` - limit number of redirects (default: `5`) | |||||
""" | |||||
alias Pleroma.Gun.ConnectionPool | |||||
@behaviour Tesla.Middleware | |||||
@max_redirects 5 | |||||
@redirect_statuses [301, 302, 303, 307, 308] | |||||
@impl Tesla.Middleware | |||||
def call(env, next, opts \\ []) do | |||||
max = Keyword.get(opts, :max_redirects, @max_redirects) | |||||
redirect(env, next, max) | |||||
end | |||||
defp redirect(env, next, left) do | |||||
opts = env.opts[:adapter] | |||||
case Tesla.run(env, next) do | |||||
{:ok, %{status: status} = res} when status in @redirect_statuses and left > 0 -> | |||||
release_conn(opts) | |||||
case Tesla.get_header(res, "location") do | |||||
nil -> | |||||
{:ok, res} | |||||
location -> | |||||
location = parse_location(location, res) | |||||
case get_conn(location, opts) do | |||||
{:ok, opts} -> | |||||
%{env | opts: Keyword.put(env.opts, :adapter, opts)} | |||||
|> new_request(res.status, location) | |||||
|> redirect(next, left - 1) | |||||
e -> | |||||
e | |||||
end | |||||
end | |||||
{:ok, %{status: status}} when status in @redirect_statuses -> | |||||
release_conn(opts) | |||||
{:error, {__MODULE__, :too_many_redirects}} | |||||
other -> | |||||
unless opts[:body_as] == :chunks do | |||||
release_conn(opts) | |||||
end | |||||
other | |||||
end | |||||
end | |||||
defp get_conn(location, opts) do | |||||
uri = URI.parse(location) | |||||
case ConnectionPool.get_conn(uri, opts) do | |||||
{:ok, conn} -> | |||||
{:ok, Keyword.merge(opts, conn: conn)} | |||||
e -> | |||||
e | |||||
end | |||||
end | |||||
defp release_conn(opts) do | |||||
ConnectionPool.release_conn(opts[:conn]) | |||||
end | |||||
# The 303 (See Other) redirect was added in HTTP/1.1 to indicate that the originally | |||||
# requested resource is not available, however a related resource (or another redirect) | |||||
# available via GET is available at the specified location. | |||||
# https://tools.ietf.org/html/rfc7231#section-6.4.4 | |||||
defp new_request(env, 303, location), do: %{env | url: location, method: :get, query: []} | |||||
# The 307 (Temporary Redirect) status code indicates that the target | |||||
# resource resides temporarily under a different URI and the user agent | |||||
# MUST NOT change the request method (...) | |||||
# https://tools.ietf.org/html/rfc7231#section-6.4.7 | |||||
defp new_request(env, 307, location), do: %{env | url: location} | |||||
defp new_request(env, _, location), do: %{env | url: location, query: []} | |||||
defp parse_location("https://" <> _rest = location, _env), do: location | |||||
defp parse_location("http://" <> _rest = location, _env), do: location | |||||
defp parse_location(location, env) do | |||||
env.url | |||||
|> URI.parse() | |||||
|> URI.merge(location) | |||||
|> URI.to_string() | |||||
end | |||||
end |