aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/http
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/http')
-rw-r--r--lib/pleroma/http/adapter_helper.ex121
-rw-r--r--lib/pleroma/http/adapter_helper/default.ex18
-rw-r--r--lib/pleroma/http/adapter_helper/gun.ex82
-rw-r--r--lib/pleroma/http/adapter_helper/hackney.ex34
-rw-r--r--lib/pleroma/http/ex_aws.ex24
-rw-r--r--lib/pleroma/http/request.ex23
-rw-r--r--lib/pleroma/http/request_builder.ex95
-rw-r--r--lib/pleroma/http/tzdata.ex29
-rw-r--r--lib/pleroma/http/web_push.ex12
9 files changed, 438 insertions, 0 deletions
diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex
new file mode 100644
index 0000000..252a6ab
--- /dev/null
+++ b/lib/pleroma/http/adapter_helper.ex
@@ -0,0 +1,121 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper do
+ @moduledoc """
+ Configure Tesla.Client with default and customized adapter options.
+ """
+ @defaults [pool: :federation, connect_timeout: 5_000, recv_timeout: 5_000]
+
+ @type proxy_type() :: :socks4 | :socks5
+ @type host() :: charlist() | :inet.ip_address()
+
+ alias Pleroma.HTTP.AdapterHelper
+ require Logger
+
+ @type proxy ::
+ {Connection.host(), pos_integer()}
+ | {Connection.proxy_type(), Connection.host(), pos_integer()}
+
+ @callback options(keyword(), URI.t()) :: keyword()
+
+ @spec format_proxy(String.t() | tuple() | nil) :: proxy() | nil
+ def format_proxy(nil), do: nil
+
+ def format_proxy(proxy_url) do
+ case parse_proxy(proxy_url) do
+ {:ok, host, port} -> {host, port}
+ {:ok, type, host, port} -> {type, host, port}
+ _ -> nil
+ end
+ end
+
+ @spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
+ def maybe_add_proxy(opts, nil), do: opts
+ def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
+
+ @doc """
+ Merge default connection & adapter options with received ones.
+ """
+
+ @spec options(URI.t(), keyword()) :: keyword()
+ def options(%URI{} = uri, opts \\ []) do
+ @defaults
+ |> Keyword.merge(opts)
+ |> adapter_helper().options(uri)
+ end
+
+ defp adapter, do: Application.get_env(:tesla, :adapter)
+
+ defp adapter_helper do
+ case adapter() do
+ Tesla.Adapter.Gun -> AdapterHelper.Gun
+ Tesla.Adapter.Hackney -> AdapterHelper.Hackney
+ _ -> AdapterHelper.Default
+ end
+ end
+
+ @spec parse_proxy(String.t() | tuple() | nil) ::
+ {:ok, host(), pos_integer()}
+ | {:ok, proxy_type(), host(), pos_integer()}
+ | {:error, atom()}
+ | nil
+
+ def parse_proxy(nil), do: nil
+
+ def parse_proxy(proxy) when is_binary(proxy) do
+ with [host, port] <- String.split(proxy, ":"),
+ {port, ""} <- Integer.parse(port) do
+ {:ok, parse_host(host), port}
+ else
+ {_, _} ->
+ Logger.warn("Parsing port failed #{inspect(proxy)}")
+ {:error, :invalid_proxy_port}
+
+ :error ->
+ Logger.warn("Parsing port failed #{inspect(proxy)}")
+ {:error, :invalid_proxy_port}
+
+ _ ->
+ Logger.warn("Parsing proxy failed #{inspect(proxy)}")
+ {:error, :invalid_proxy}
+ end
+ end
+
+ def parse_proxy(proxy) when is_tuple(proxy) do
+ with {type, host, port} <- proxy do
+ {:ok, type, parse_host(host), port}
+ else
+ _ ->
+ Logger.warn("Parsing proxy failed #{inspect(proxy)}")
+ {:error, :invalid_proxy}
+ end
+ end
+
+ @spec parse_host(String.t() | atom() | charlist()) :: charlist() | :inet.ip_address()
+ def parse_host(host) when is_list(host), do: host
+ def parse_host(host) when is_atom(host), do: to_charlist(host)
+
+ def parse_host(host) when is_binary(host) do
+ host = to_charlist(host)
+
+ case :inet.parse_address(host) do
+ {:error, :einval} -> host
+ {:ok, ip} -> ip
+ end
+ end
+
+ @spec format_host(String.t()) :: charlist()
+ def format_host(host) do
+ host_charlist = to_charlist(host)
+
+ case :inet.parse_address(host_charlist) do
+ {:error, :einval} ->
+ :idna.encode(host_charlist)
+
+ {:ok, _ip} ->
+ host_charlist
+ end
+ end
+end
diff --git a/lib/pleroma/http/adapter_helper/default.ex b/lib/pleroma/http/adapter_helper/default.ex
new file mode 100644
index 0000000..9c94147
--- /dev/null
+++ b/lib/pleroma/http/adapter_helper/default.ex
@@ -0,0 +1,18 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper.Default do
+ alias Pleroma.HTTP.AdapterHelper
+
+ @behaviour Pleroma.HTTP.AdapterHelper
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(opts, _uri) do
+ proxy = Pleroma.Config.get([:http, :proxy_url], nil)
+ AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy))
+ end
+
+ @spec get_conn(URI.t(), keyword()) :: {:ok, keyword()}
+ def get_conn(_uri, opts), do: {:ok, opts}
+end
diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex
new file mode 100644
index 0000000..74ab985
--- /dev/null
+++ b/lib/pleroma/http/adapter_helper/gun.ex
@@ -0,0 +1,82 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper.Gun do
+ @behaviour Pleroma.HTTP.AdapterHelper
+
+ alias Pleroma.Config
+ alias Pleroma.HTTP.AdapterHelper
+
+ require Logger
+
+ @defaults [
+ retry: 1,
+ retry_timeout: 1_000
+ ]
+
+ @type pool() :: :federation | :upload | :media | :default
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(incoming_opts \\ [], %URI{} = uri) do
+ proxy =
+ [:http, :proxy_url]
+ |> Config.get()
+ |> AdapterHelper.format_proxy()
+
+ config_opts = Config.get([:http, :adapter], [])
+
+ @defaults
+ |> Keyword.merge(config_opts)
+ |> add_scheme_opts(uri)
+ |> AdapterHelper.maybe_add_proxy(proxy)
+ |> Keyword.merge(incoming_opts)
+ |> put_timeout()
+ end
+
+ defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
+
+ defp add_scheme_opts(opts, %{scheme: "https"}) do
+ Keyword.put(opts, :certificates_verification, true)
+ end
+
+ defp put_timeout(opts) do
+ {recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool]))
+ # this is the timeout to receive a message from Gun
+ # `:timeout` key is used in Tesla
+ Keyword.put(opts, :timeout, recv_timeout)
+ end
+
+ @spec pool_timeout(pool()) :: non_neg_integer()
+ def pool_timeout(pool) do
+ default = Config.get([:pools, :default, :recv_timeout], 5_000)
+
+ Config.get([:pools, pool, :recv_timeout], default)
+ end
+
+ def limiter_setup do
+ prefix = Pleroma.Gun.ConnectionPool
+ wait = Config.get([:connections_pool, :connection_acquisition_wait])
+ retries = Config.get([:connections_pool, :connection_acquisition_retries])
+
+ :pools
+ |> Config.get([])
+ |> Enum.each(fn {name, opts} ->
+ max_running = Keyword.get(opts, :size, 50)
+ max_waiting = Keyword.get(opts, :max_waiting, 10)
+
+ result =
+ ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting,
+ wait: wait,
+ max_retries: retries
+ )
+
+ case result do
+ :ok -> :ok
+ {:error, :existing} -> :ok
+ end
+ end)
+
+ :ok
+ end
+end
diff --git a/lib/pleroma/http/adapter_helper/hackney.ex b/lib/pleroma/http/adapter_helper/hackney.ex
new file mode 100644
index 0000000..15a84cf
--- /dev/null
+++ b/lib/pleroma/http/adapter_helper/hackney.ex
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.AdapterHelper.Hackney do
+ @behaviour Pleroma.HTTP.AdapterHelper
+
+ @defaults [
+ ]
+
+ @spec options(keyword(), URI.t()) :: keyword()
+ def options(connection_opts \\ [], %URI{} = uri) do
+ proxy = Pleroma.Config.get([:http, :proxy_url])
+
+ config_opts = Pleroma.Config.get([:http, :adapter], [])
+
+ @defaults
+ |> Keyword.merge(config_opts)
+ |> Keyword.merge(connection_opts)
+ |> add_scheme_opts(uri)
+ |> maybe_add_with_body()
+ |> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
+ end
+
+ defp add_scheme_opts(opts, _), do: opts
+
+ defp maybe_add_with_body(opts) do
+ if opts[:max_body] do
+ Keyword.put(opts, :with_body, true)
+ else
+ opts
+ end
+ end
+end
diff --git a/lib/pleroma/http/ex_aws.ex b/lib/pleroma/http/ex_aws.ex
new file mode 100644
index 0000000..469c138
--- /dev/null
+++ b/lib/pleroma/http/ex_aws.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.ExAws do
+ @moduledoc false
+
+ @behaviour ExAws.Request.HttpClient
+
+ alias Pleroma.HTTP
+
+ @impl true
+ def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do
+ http_opts = Keyword.put_new(http_opts, :pool, :upload)
+
+ case HTTP.request(method, url, body, headers, http_opts) do
+ {:ok, env} ->
+ {:ok, %{status_code: env.status, headers: env.headers, body: env.body}}
+
+ {:error, reason} ->
+ {:error, %{reason: reason}}
+ end
+ end
+end
diff --git a/lib/pleroma/http/request.ex b/lib/pleroma/http/request.ex
new file mode 100644
index 0000000..01045f8
--- /dev/null
+++ b/lib/pleroma/http/request.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Request do
+ @moduledoc """
+ Request struct.
+ """
+ defstruct method: :get, url: "", query: [], headers: [], body: "", opts: []
+
+ @type method :: :head | :get | :delete | :trace | :options | :post | :put | :patch
+ @type url :: String.t()
+ @type headers :: [{String.t(), String.t()}]
+
+ @type t :: %__MODULE__{
+ method: method(),
+ url: url(),
+ query: keyword(),
+ headers: headers(),
+ body: String.t(),
+ opts: keyword()
+ }
+end
diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex
new file mode 100644
index 0000000..f16fb3b
--- /dev/null
+++ b/lib/pleroma/http/request_builder.ex
@@ -0,0 +1,95 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.RequestBuilder do
+ @moduledoc """
+ Helper functions for building Tesla requests
+ """
+
+ alias Pleroma.HTTP.Request
+ alias Tesla.Multipart
+
+ @doc """
+ Creates new request
+ """
+ @spec new(Request.t()) :: Request.t()
+ def new(%Request{} = request \\ %Request{}), do: request
+
+ @doc """
+ Specify the request method when building a request
+ """
+ @spec method(Request.t(), Request.method()) :: Request.t()
+ def method(request, m), do: %{request | method: m}
+
+ @doc """
+ Specify the request method when building a request
+ """
+ @spec url(Request.t(), Request.url()) :: Request.t()
+ def url(request, u), do: %{request | url: u}
+
+ @doc """
+ Add headers to the request
+ """
+ @spec headers(Request.t(), Request.headers()) :: Request.t()
+ def headers(request, headers) do
+ headers_list =
+ with true <- Pleroma.Config.get([:http, :send_user_agent]),
+ nil <- Enum.find(headers, fn {key, _val} -> String.downcase(key) == "user-agent" end) do
+ [{"user-agent", Pleroma.Application.user_agent()} | headers]
+ else
+ _ ->
+ headers
+ end
+
+ %{request | headers: headers_list}
+ end
+
+ @doc """
+ Add custom, per-request middleware or adapter options to the request
+ """
+ @spec opts(Request.t(), keyword()) :: Request.t()
+ def opts(request, options), do: %{request | opts: options}
+
+ @doc """
+ Add optional parameters to the request
+ """
+ @spec add_param(Request.t(), atom(), atom(), any()) :: Request.t()
+ def add_param(request, :query, :query, values), do: %{request | query: values}
+
+ def add_param(request, :body, :body, value), do: %{request | body: value}
+
+ def add_param(request, :body, key, value) do
+ request
+ |> Map.put(:body, Multipart.new())
+ |> Map.update!(
+ :body,
+ &Multipart.add_field(
+ &1,
+ key,
+ Jason.encode!(value),
+ headers: [{"content-type", "application/json"}]
+ )
+ )
+ end
+
+ def add_param(request, :file, name, path) do
+ request
+ |> Map.put(:body, Multipart.new())
+ |> Map.update!(:body, &Multipart.add_file(&1, path, name: name))
+ end
+
+ def add_param(request, :form, name, value) do
+ Map.update(request, :body, %{name => value}, &Map.put(&1, name, value))
+ end
+
+ def add_param(request, location, key, value) do
+ Map.update(request, location, [{key, value}], &(&1 ++ [{key, value}]))
+ end
+
+ def convert_to_keyword(request) do
+ request
+ |> Map.from_struct()
+ |> Enum.into([])
+ end
+end
diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex
new file mode 100644
index 0000000..5d2529c
--- /dev/null
+++ b/lib/pleroma/http/tzdata.ex
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Tzdata do
+ @moduledoc false
+
+ @behaviour Tzdata.HTTPClient
+
+ alias Pleroma.HTTP
+
+ @impl true
+ def get(url, headers, options) do
+ options = Keyword.put_new(options, :pool, :default)
+
+ with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
+ {:ok, {env.status, env.headers, env.body}}
+ end
+ end
+
+ @impl true
+ def head(url, headers, options) do
+ options = Keyword.put_new(options, :pool, :default)
+
+ with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
+ {:ok, {env.status, env.headers}}
+ end
+ end
+end
diff --git a/lib/pleroma/http/web_push.ex b/lib/pleroma/http/web_push.ex
new file mode 100644
index 0000000..ca399b6
--- /dev/null
+++ b/lib/pleroma/http/web_push.ex
@@ -0,0 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.WebPush do
+ @moduledoc false
+
+ def post(url, payload, headers, options \\ []) do
+ list_headers = Map.to_list(headers)
+ Pleroma.HTTP.post(url, payload, list_headers, options)
+ end
+end