1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Object.Fetcher do
7 alias Pleroma.Instances
10 alias Pleroma.Object.Containment
11 alias Pleroma.Signature
12 alias Pleroma.Web.ActivityPub.InternalFetchActor
13 alias Pleroma.Web.ActivityPub.MRF
14 alias Pleroma.Web.ActivityPub.ObjectValidator
15 alias Pleroma.Web.ActivityPub.Pipeline
16 alias Pleroma.Web.ActivityPub.Transmogrifier
17 alias Pleroma.Web.Federator
20 require Pleroma.Constants
22 @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
23 defp reinject_object(%Object{data: %{}} = object, new_data) do
24 Logger.debug("Reinjecting object #{new_data["id"]}")
26 with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}),
27 {:ok, new_data} <- MRF.filter(new_data),
28 {:ok, new_object, _} <-
29 Object.Updater.do_update_and_invalidate_cache(
32 _touch_changeset? = true
37 Logger.error("Error while processing object: #{inspect(e)}")
42 defp reinject_object(_, new_data) do
43 with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do
50 def refetch_object(%Object{data: %{"id" => id}} = object) do
51 with {:local, false} <- {:local, Object.local?(object)},
52 {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
53 {:ok, object} <- reinject_object(object, new_data) do
56 {:local, true} -> {:ok, object}
61 # Note: will create a Create activity, which we need internally at the moment.
62 def fetch_object_from_id(id, options \\ []) do
63 with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
64 {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
65 {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
66 {_, nil} <- {:normalize, Object.normalize(data, fetch: false)},
67 params <- prepare_activity_params(data),
68 {_, :ok} <- {:containment, Containment.contain_origin(id, params)},
69 {_, {:ok, activity}} <-
70 {:transmogrifier, Transmogrifier.handle_incoming(params, options)},
71 {_, _data, %Object{} = object} <-
72 {:object, data, Object.normalize(activity, fetch: false)} do
75 {:allowed_depth, false} ->
76 {:error, "Max thread distance exceeded."}
79 {:error, "Object containment failed."}
81 {:transmogrifier, {:error, {:reject, e}}} ->
84 {:transmogrifier, {:reject, e}} ->
87 {:transmogrifier, _} = e ->
90 {:object, data, nil} ->
91 reinject_object(%Object{}, data)
93 {:normalize, object = %Object{}} ->
96 {:fetch_object, %Object{} = object} ->
99 {:fetch, {:error, error}} ->
107 defp prepare_activity_params(data) do
110 # Should we seriously keep this attributedTo thing?
111 "actor" => data["actor"] || data["attributedTo"],
114 |> Maps.put_if_present("to", data["to"])
115 |> Maps.put_if_present("cc", data["cc"])
116 |> Maps.put_if_present("bto", data["bto"])
117 |> Maps.put_if_present("bcc", data["bcc"])
120 def fetch_object_from_id!(id, options \\ []) do
121 with {:ok, object} <- fetch_object_from_id(id, options) do
124 {:error, %Tesla.Mock.Error{}} ->
127 {:error, "Object has been deleted"} ->
131 Logger.info("Rejected #{id} while fetching: #{inspect(reason)}")
135 Logger.error("Error while fetching #{id}: #{inspect(e)}")
140 defp make_signature(id, date) do
144 InternalFetchActor.get_actor()
146 "(request-target)": "get #{uri.path}",
151 {"signature", signature}
154 defp sign_fetch(headers, id, date) do
155 if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
156 [make_signature(id, date) | headers]
162 defp maybe_date_fetch(headers, date) do
163 if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
164 [{"date", date} | headers]
170 def fetch_and_contain_remote_object_from_id(id)
172 def fetch_and_contain_remote_object_from_id(%{"id" => id}),
173 do: fetch_and_contain_remote_object_from_id(id)
175 def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
176 Logger.debug("Fetching object #{id} via AP")
178 with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
179 {:ok, body} <- get_object(id),
180 {:ok, data} <- safe_json_decode(body),
181 :ok <- Containment.contain_origin_from_id(id, data) do
182 if not Instances.reachable?(id) do
183 Instances.set_reachable(id)
189 {:error, "Unsupported URI scheme"}
199 def fetch_and_contain_remote_object_from_id(_id),
200 do: {:error, "id must be a string"}
202 defp get_object(id) do
203 date = Pleroma.Signature.signed_date()
206 [{"accept", "application/activity+json"}]
207 |> maybe_date_fetch(date)
208 |> sign_fetch(id, date)
210 case HTTP.get(id, headers) do
211 {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
212 case List.keyfind(headers, "content-type", 0) do
214 case Plug.Conn.Utils.media_type(content_type) do
215 {:ok, "application", "activity+json", _} ->
218 {:ok, "application", "ld+json",
219 %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
223 {:error, {:content_type, content_type}}
227 {:error, {:content_type, nil}}
230 {:ok, %{status: code}} when code in [404, 410] ->
231 {:error, "Object has been deleted"}
241 defp safe_json_decode(nil), do: {:ok, nil}
242 defp safe_json_decode(json), do: Jason.decode(json)