77f78c7fff17294266c5d54a63a9b01bbc78cb00
[akkoma] / lib / pleroma / gun / conn.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.Gun.Conn do
6 alias Pleroma.Gun
7
8 require Logger
9
10 def open(%URI{} = uri, opts) do
11 pool_opts = Pleroma.Config.get([:connections_pool], [])
12
13 opts =
14 opts
15 |> Enum.into(%{})
16 |> Map.put_new(:retry, pool_opts[:retry] || 1)
17 |> Map.put_new(:retry_timeout, pool_opts[:retry_timeout] || 1000)
18 |> Map.put_new(:await_up_timeout, pool_opts[:await_up_timeout] || 5_000)
19 |> Map.put_new(:supervise, false)
20 |> maybe_add_tls_opts(uri)
21
22 do_open(uri, opts)
23 end
24
25 defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts
26
27 defp maybe_add_tls_opts(opts, %URI{scheme: "https", host: host}) do
28 tls_opts = [
29 verify: :verify_peer,
30 cacertfile: CAStore.file_path(),
31 depth: 20,
32 reuse_sessions: false,
33 verify_fun:
34 {&:ssl_verify_hostname.verify_fun/3,
35 [check_hostname: Pleroma.HTTP.AdapterHelper.format_host(host)]}
36 ]
37
38 tls_opts =
39 if Keyword.keyword?(opts[:tls_opts]) do
40 Keyword.merge(tls_opts, opts[:tls_opts])
41 else
42 tls_opts
43 end
44
45 Map.put(opts, :tls_opts, tls_opts)
46 end
47
48 defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
49 connect_opts =
50 uri
51 |> destination_opts()
52 |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
53
54 with open_opts <- Map.delete(opts, :tls_opts),
55 {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
56 {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]),
57 stream <- Gun.connect(conn, connect_opts),
58 {:response, :fin, 200, _} <- Gun.await(conn, stream) do
59 {:ok, conn}
60 else
61 error ->
62 Logger.warn(
63 "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{
64 inspect(error)
65 }"
66 )
67
68 error
69 end
70 end
71
72 defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
73 version =
74 proxy_type
75 |> to_string()
76 |> String.last()
77 |> case do
78 "4" -> 4
79 _ -> 5
80 end
81
82 socks_opts =
83 uri
84 |> destination_opts()
85 |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
86 |> Map.put(:version, version)
87
88 opts =
89 opts
90 |> Map.put(:protocols, [:socks])
91 |> Map.put(:socks_opts, socks_opts)
92
93 with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
94 {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
95 {:ok, conn}
96 else
97 error ->
98 Logger.warn(
99 "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{
100 inspect(error)
101 }"
102 )
103
104 error
105 end
106 end
107
108 defp do_open(%URI{host: host, port: port} = uri, opts) do
109 host = Pleroma.HTTP.AdapterHelper.parse_host(host)
110
111 with {:ok, conn} <- Gun.open(host, port, opts),
112 {:ok, _} <- Gun.await_up(conn, opts[:await_up_timeout]) do
113 {:ok, conn}
114 else
115 error ->
116 Logger.warn(
117 "Opening connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
118 )
119
120 error
121 end
122 end
123
124 defp destination_opts(%URI{host: host, port: port}) do
125 host = Pleroma.HTTP.AdapterHelper.parse_host(host)
126 %{host: host, port: port}
127 end
128
129 defp add_http2_opts(opts, "https", tls_opts) do
130 Map.merge(opts, %{protocols: [:http2], transport: :tls, tls_opts: tls_opts})
131 end
132
133 defp add_http2_opts(opts, _, _), do: opts
134
135 def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
136 "#{scheme}://#{host}#{path}"
137 end
138 end