total rebase
[anni] / lib / pleroma / web / rich_media / parser.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.Web.RichMedia.Parser do
6   require Logger
7
8   @config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
9
10   defp parsers do
11     Pleroma.Config.get([:rich_media, :parsers])
12   end
13
14   def parse(nil), do: nil
15
16   @spec parse(String.t()) :: {:ok, map()} | {:error, any()}
17   def parse(url) do
18     with :ok <- validate_page_url(url),
19          {:ok, data} <- parse_url(url) do
20       data = Map.put(data, "url", url)
21       {:ok, data}
22     end
23   end
24
25   defp parse_url(url) do
26     with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url),
27          {:ok, html} <- Floki.parse_document(html) do
28       html
29       |> maybe_parse()
30       |> clean_parsed_data()
31       |> check_parsed_data()
32     end
33   end
34
35   defp maybe_parse(html) do
36     Enum.reduce_while(parsers(), %{}, fn parser, acc ->
37       case parser.parse(html, acc) do
38         data when data != %{} -> {:halt, data}
39         _ -> {:cont, acc}
40       end
41     end)
42   end
43
44   defp check_parsed_data(%{"title" => title} = data)
45        when is_binary(title) and title != "" do
46     {:ok, data}
47   end
48
49   defp check_parsed_data(data) do
50     {:error, {:invalid_metadata, data}}
51   end
52
53   defp clean_parsed_data(data) do
54     data
55     |> Enum.reject(fn {key, val} ->
56       not match?({:ok, _}, Jason.encode(%{key => val}))
57     end)
58     |> Map.new()
59   end
60
61   @spec validate_page_url(URI.t() | binary()) :: :ok | :error
62   defp validate_page_url(page_url) when is_binary(page_url) do
63     validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld])
64
65     page_url
66     |> Linkify.Parser.url?(validate_tld: validate_tld)
67     |> parse_uri(page_url)
68   end
69
70   defp validate_page_url(%URI{host: host, scheme: "https"}) do
71     cond do
72       Linkify.Parser.ip?(host) ->
73         :error
74
75       host in @config_impl.get([:rich_media, :ignore_hosts], []) ->
76         :error
77
78       get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) ->
79         :error
80
81       true ->
82         :ok
83     end
84   end
85
86   defp validate_page_url(_), do: :error
87
88   defp parse_uri(true, url) do
89     url
90     |> URI.parse()
91     |> validate_page_url
92   end
93
94   defp parse_uri(_, _), do: :error
95
96   defp get_tld(host) do
97     host
98     |> String.split(".")
99     |> Enum.reverse()
100     |> hd
101   end
102 end