First
[anni] / lib / pleroma / gun / conn.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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(:connect_timeout, pool_opts[:connect_timeout] || 5_000)
17       |> Map.put_new(:supervise, false)
18       |> maybe_add_tls_opts(uri)
19
20     do_open(uri, opts)
21   end
22
23   defp maybe_add_tls_opts(opts, %URI{scheme: "http"}), do: opts
24
25   defp maybe_add_tls_opts(opts, %URI{scheme: "https"}) do
26     tls_opts = [
27       verify: :verify_peer,
28       cacertfile: CAStore.file_path(),
29       depth: 20,
30       reuse_sessions: false,
31       log_level: :warning,
32       customize_hostname_check: [match_fun: :public_key.pkix_verify_hostname_match_fun(:https)]
33     ]
34
35     tls_opts =
36       if Keyword.keyword?(opts[:tls_opts]) do
37         Keyword.merge(tls_opts, opts[:tls_opts])
38       else
39         tls_opts
40       end
41
42     Map.put(opts, :tls_opts, tls_opts)
43   end
44
45   defp do_open(uri, %{proxy: {proxy_host, proxy_port}} = opts) do
46     connect_opts =
47       uri
48       |> destination_opts()
49       |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
50
51     with open_opts <- Map.delete(opts, :tls_opts),
52          {:ok, conn} <- Gun.open(proxy_host, proxy_port, open_opts),
53          {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]),
54          stream <- Gun.connect(conn, connect_opts),
55          {:response, :fin, 200, _} <- Gun.await(conn, stream) do
56       {:ok, conn, protocol}
57     else
58       error ->
59         Logger.warn(
60           "Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
61         )
62
63         error
64     end
65   end
66
67   defp do_open(uri, %{proxy: {proxy_type, proxy_host, proxy_port}} = opts) do
68     version =
69       proxy_type
70       |> to_string()
71       |> String.last()
72       |> case do
73         "4" -> 4
74         _ -> 5
75       end
76
77     socks_opts =
78       uri
79       |> destination_opts()
80       |> add_http2_opts(uri.scheme, Map.get(opts, :tls_opts, []))
81       |> Map.put(:version, version)
82
83     opts =
84       opts
85       |> Map.put(:protocols, [:socks])
86       |> Map.put(:socks_opts, socks_opts)
87
88     with {:ok, conn} <- Gun.open(proxy_host, proxy_port, opts),
89          {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do
90       {:ok, conn, protocol}
91     else
92       error ->
93         Logger.warn(
94           "Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
95         )
96
97         error
98     end
99   end
100
101   defp do_open(%URI{host: host, port: port} = uri, opts) do
102     host = Pleroma.HTTP.AdapterHelper.parse_host(host)
103
104     with {:ok, conn} <- Gun.open(host, port, opts),
105          {:ok, protocol} <- Gun.await_up(conn, opts[:connect_timeout]) do
106       {:ok, conn, protocol}
107     else
108       error ->
109         Logger.warn(
110           "Opening connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
111         )
112
113         error
114     end
115   end
116
117   defp destination_opts(%URI{host: host, port: port}) do
118     host = Pleroma.HTTP.AdapterHelper.parse_host(host)
119     %{host: host, port: port}
120   end
121
122   defp add_http2_opts(opts, "https", tls_opts) do
123     Map.merge(opts, %{protocols: [:http2], transport: :tls, tls_opts: tls_opts})
124   end
125
126   defp add_http2_opts(opts, _, _), do: opts
127
128   def compose_uri_log(%URI{scheme: scheme, host: host, path: path}) do
129     "#{scheme}://#{host}#{path}"
130   end
131 end