logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/

fetcher.ex (7941B)


  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. defmodule Pleroma.Object.Fetcher do
  5. alias Pleroma.HTTP
  6. alias Pleroma.Instances
  7. alias Pleroma.Maps
  8. alias Pleroma.Object
  9. alias Pleroma.Object.Containment
  10. alias Pleroma.Signature
  11. alias Pleroma.Web.ActivityPub.InternalFetchActor
  12. alias Pleroma.Web.ActivityPub.MRF
  13. alias Pleroma.Web.ActivityPub.ObjectValidator
  14. alias Pleroma.Web.ActivityPub.Pipeline
  15. alias Pleroma.Web.ActivityPub.Transmogrifier
  16. alias Pleroma.Web.Federator
  17. require Logger
  18. require Pleroma.Constants
  19. @mix_env Mix.env()
  20. @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
  21. defp reinject_object(%Object{data: %{}} = object, new_data) do
  22. Logger.debug("Reinjecting object #{new_data["id"]}")
  23. with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}),
  24. {:ok, new_data} <- MRF.filter(new_data),
  25. {:ok, new_object, _} <-
  26. Object.Updater.do_update_and_invalidate_cache(
  27. object,
  28. new_data,
  29. _touch_changeset? = true
  30. ) do
  31. {:ok, new_object}
  32. else
  33. e ->
  34. Logger.error("Error while processing object: #{inspect(e)}")
  35. {:error, e}
  36. end
  37. end
  38. defp reinject_object(_, new_data) do
  39. with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do
  40. {:ok, object}
  41. else
  42. e -> e
  43. end
  44. end
  45. def refetch_object(%Object{data: %{"id" => id}} = object) do
  46. with {:local, false} <- {:local, Object.local?(object)},
  47. {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
  48. {:ok, object} <- reinject_object(object, new_data) do
  49. {:ok, object}
  50. else
  51. {:local, true} -> {:ok, object}
  52. e -> {:error, e}
  53. end
  54. end
  55. @typep fetcher_errors ::
  56. :error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier
  57. # Note: will create a Create activity, which we need internally at the moment.
  58. @spec fetch_object_from_id(String.t(), list()) ::
  59. {:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors()
  60. def fetch_object_from_id(id, options \\ []) do
  61. with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
  62. {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
  63. {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
  64. {_, nil} <- {:normalize, Object.normalize(data, fetch: false)},
  65. params <- prepare_activity_params(data),
  66. {_, :ok} <- {:containment, Containment.contain_origin(id, params)},
  67. {_, {:ok, activity}} <-
  68. {:transmogrifier, Transmogrifier.handle_incoming(params, options)},
  69. {_, _data, %Object{} = object} <-
  70. {:object, data, Object.normalize(activity, fetch: false)} do
  71. {:ok, object}
  72. else
  73. {:normalize, object = %Object{}} ->
  74. {:ok, object}
  75. {:fetch_object, %Object{} = object} ->
  76. {:ok, object}
  77. {:object, data, nil} ->
  78. reinject_object(%Object{}, data)
  79. e ->
  80. Logger.metadata(object: id)
  81. Logger.error("Object rejected while fetching #{id} #{inspect(e)}")
  82. e
  83. end
  84. end
  85. defp prepare_activity_params(data) do
  86. %{
  87. "type" => "Create",
  88. # Should we seriously keep this attributedTo thing?
  89. "actor" => data["actor"] || data["attributedTo"],
  90. "object" => data
  91. }
  92. |> Maps.put_if_present("to", data["to"])
  93. |> Maps.put_if_present("cc", data["cc"])
  94. |> Maps.put_if_present("bto", data["bto"])
  95. |> Maps.put_if_present("bcc", data["bcc"])
  96. end
  97. defp make_signature(id, date) do
  98. uri = URI.parse(id)
  99. signature =
  100. InternalFetchActor.get_actor()
  101. |> Signature.sign(%{
  102. "(request-target)": "get #{uri.path}",
  103. host: uri.host,
  104. date: date
  105. })
  106. {"signature", signature}
  107. end
  108. defp sign_fetch(headers, id, date) do
  109. if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
  110. [make_signature(id, date) | headers]
  111. else
  112. headers
  113. end
  114. end
  115. defp maybe_date_fetch(headers, date) do
  116. if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
  117. [{"date", date} | headers]
  118. else
  119. headers
  120. end
  121. end
  122. def fetch_and_contain_remote_object_from_id(id)
  123. def fetch_and_contain_remote_object_from_id(%{"id" => id}),
  124. do: fetch_and_contain_remote_object_from_id(id)
  125. def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
  126. Logger.debug("Fetching object #{id} via AP")
  127. with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
  128. {_, true} <- {:mrf, MRF.id_filter(id)},
  129. {_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
  130. {:ok, body} <- get_object(id),
  131. {:ok, data} <- safe_json_decode(body),
  132. :ok <- Containment.contain_origin_from_id(id, data) do
  133. if not Instances.reachable?(id) do
  134. Instances.set_reachable(id)
  135. end
  136. {:ok, data}
  137. else
  138. {:scheme, _} ->
  139. {:error, "Unsupported URI scheme"}
  140. {:local_fetch, _} ->
  141. {:error, "Trying to fetch local resource"}
  142. {:error, e} ->
  143. {:error, e}
  144. {:mrf, false} ->
  145. {:error, {:reject, "Filtered by id"}}
  146. e ->
  147. {:error, e}
  148. end
  149. end
  150. def fetch_and_contain_remote_object_from_id(_id),
  151. do: {:error, "id must be a string"}
  152. defp check_crossdomain_redirect(final_host, original_url)
  153. # Handle the common case in tests where responses don't include URLs
  154. if @mix_env == :test do
  155. defp check_crossdomain_redirect(nil, _) do
  156. {:cross_domain_redirect, false}
  157. end
  158. end
  159. defp check_crossdomain_redirect(final_host, original_url) do
  160. {:cross_domain_redirect, final_host != URI.parse(original_url).host}
  161. end
  162. defp get_object(id) do
  163. date = Pleroma.Signature.signed_date()
  164. headers =
  165. [{"accept", "application/activity+json"}]
  166. |> maybe_date_fetch(date)
  167. |> sign_fetch(id, date)
  168. case HTTP.get(id, headers) do
  169. {:ok, %{body: body, status: code, headers: headers, url: final_url}}
  170. when code in 200..299 ->
  171. remote_host = if final_url, do: URI.parse(final_url).host, else: nil
  172. with {:cross_domain_redirect, false} <- check_crossdomain_redirect(remote_host, id),
  173. {_, content_type} <- List.keyfind(headers, "content-type", 0),
  174. {:ok, _media_type} <- verify_content_type(content_type) do
  175. {:ok, body}
  176. else
  177. {:cross_domain_redirect, true} ->
  178. {:error, {:cross_domain_redirect, true}}
  179. error ->
  180. error
  181. end
  182. # Handle the case where URL is not in the response (older HTTP library versions)
  183. {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
  184. case List.keyfind(headers, "content-type", 0) do
  185. {_, content_type} ->
  186. case verify_content_type(content_type) do
  187. {:ok, _} -> {:ok, body}
  188. error -> error
  189. end
  190. _ ->
  191. {:error, {:content_type, nil}}
  192. end
  193. {:ok, %{status: code}} when code in [401, 403] ->
  194. {:error, :forbidden}
  195. {:ok, %{status: code}} when code in [404, 410] ->
  196. {:error, :not_found}
  197. {:error, e} ->
  198. {:error, e}
  199. e ->
  200. {:error, e}
  201. end
  202. end
  203. defp safe_json_decode(nil), do: {:ok, nil}
  204. defp safe_json_decode(json), do: Jason.decode(json)
  205. defp verify_content_type(content_type) do
  206. case Plug.Conn.Utils.media_type(content_type) do
  207. {:ok, "application", "activity+json", _} ->
  208. {:ok, :activity_json}
  209. {:ok, "application", "ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
  210. {:ok, :ld_json}
  211. _ ->
  212. {:error, {:content_type, content_type}}
  213. end
  214. end
  215. end