To achieve this signatures are now generated by a custom Tesla Middleware placed after the FollowRedirects Middleware. Any requests which should be signed needs to pass the signing key via opts. This also unifies the associated header logic between fetching and publishing, notably resolving a divergence wrt the "host" header. Relevant spec demands the host header shall include a port identification if not using the protocols standard port. Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/731
104 lines
3.1 KiB
Elixir
104 lines
3.1 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2021 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
|
|
|
|
@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, nil, headers, options)
|
|
|
|
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
|
|
def head(url, headers \\ [], options \\ []), do: request(:head, url, nil, headers, options)
|
|
|
|
@doc """
|
|
Performs POST request.
|
|
|
|
See `Pleroma.HTTP.request/5`
|
|
"""
|
|
@spec post(Request.url(), String.t(), 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(), String.t(), 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(options || [])
|
|
|
|
adapter_opts =
|
|
if uri.scheme == :https do
|
|
AdapterHelper.maybe_add_cacerts(adapter_opts, :public_key.cacerts_get())
|
|
else
|
|
adapter_opts
|
|
end
|
|
|
|
options = put_in(options[:adapter], adapter_opts)
|
|
params = options[:params] || []
|
|
request = build_request(method, headers, options, url, body, params)
|
|
|
|
client =
|
|
Tesla.client([
|
|
Tesla.Middleware.FollowRedirects,
|
|
Pleroma.HTTP.Middleware.HTTPSignature,
|
|
Tesla.Middleware.Telemetry
|
|
])
|
|
|
|
Logger.debug("Outbound: #{method} #{url}")
|
|
request(client, request)
|
|
rescue
|
|
e ->
|
|
Logger.error("Failed to fetch #{url}: #{Exception.format(:error, e, __STACKTRACE__)}")
|
|
{:error, :fetch_error}
|
|
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
|
|
end
|