Merge branch 'length-limit-bio' into 'develop'
[akkoma] / lib / pleroma / object / fetcher.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Object.Fetcher do
6 alias Pleroma.HTTP
7 alias Pleroma.Object
8 alias Pleroma.Object.Containment
9 alias Pleroma.Signature
10 alias Pleroma.Web.ActivityPub.InternalFetchActor
11 alias Pleroma.Web.ActivityPub.Transmogrifier
12 alias Pleroma.Web.OStatus
13
14 require Logger
15
16 defp reinject_object(data) do
17 Logger.debug("Reinjecting object #{data["id"]}")
18
19 with data <- Transmogrifier.fix_object(data),
20 {:ok, object} <- Object.create(data) do
21 {:ok, object}
22 else
23 e ->
24 Logger.error("Error while processing object: #{inspect(e)}")
25 {:error, e}
26 end
27 end
28
29 # TODO:
30 # This will create a Create activity, which we need internally at the moment.
31 def fetch_object_from_id(id, options \\ []) do
32 if object = Object.get_cached_by_ap_id(id) do
33 {:ok, object}
34 else
35 Logger.info("Fetching #{id} via AP")
36
37 with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
38 {:normalize, nil} <- {:normalize, Object.normalize(data, false)},
39 params <- %{
40 "type" => "Create",
41 "to" => data["to"],
42 "cc" => data["cc"],
43 # Should we seriously keep this attributedTo thing?
44 "actor" => data["actor"] || data["attributedTo"],
45 "object" => data
46 },
47 {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
48 {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
49 {:object, _data, %Object{} = object} <-
50 {:object, data, Object.normalize(activity, false)} do
51 {:ok, object}
52 else
53 {:containment, _} ->
54 {:error, "Object containment failed."}
55
56 {:error, {:reject, nil}} ->
57 {:reject, nil}
58
59 {:object, data, nil} ->
60 reinject_object(data)
61
62 {:normalize, object = %Object{}} ->
63 {:ok, object}
64
65 _e ->
66 # Only fallback when receiving a fetch/normalization error with ActivityPub
67 Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
68
69 # FIXME: OStatus Object Containment?
70 case OStatus.fetch_activity_from_url(id) do
71 {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)}
72 e -> e
73 end
74 end
75 end
76 end
77
78 def fetch_object_from_id!(id, options \\ []) do
79 with {:ok, object} <- fetch_object_from_id(id, options) do
80 object
81 else
82 _e ->
83 nil
84 end
85 end
86
87 defp make_signature(id, date) do
88 uri = URI.parse(id)
89
90 signature =
91 InternalFetchActor.get_actor()
92 |> Signature.sign(%{
93 "(request-target)": "get #{uri.path}",
94 host: uri.host,
95 date: date
96 })
97
98 [{:Signature, signature}]
99 end
100
101 defp sign_fetch(headers, id, date) do
102 if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
103 headers ++ make_signature(id, date)
104 else
105 headers
106 end
107 end
108
109 defp maybe_date_fetch(headers, date) do
110 if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
111 headers ++ [{:Date, date}]
112 else
113 headers
114 end
115 end
116
117 def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
118 Logger.info("Fetching object #{id} via AP")
119
120 date =
121 NaiveDateTime.utc_now()
122 |> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
123
124 headers =
125 [{:Accept, "application/activity+json"}]
126 |> maybe_date_fetch(date)
127 |> sign_fetch(id, date)
128
129 Logger.debug("Fetch headers: #{inspect(headers)}")
130
131 with true <- String.starts_with?(id, "http"),
132 {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers),
133 {:ok, data} <- Jason.decode(body),
134 :ok <- Containment.contain_origin_from_id(id, data) do
135 {:ok, data}
136 else
137 {:ok, %{status: code}} when code in [404, 410] ->
138 {:error, "Object has been deleted"}
139
140 e ->
141 {:error, e}
142 end
143 end
144
145 def fetch_and_contain_remote_object_from_id(%{"id" => id}),
146 do: fetch_and_contain_remote_object_from_id(id)
147
148 def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"}
149 end