+ defp intersection(list1, list2) do
+ list1 -- list1 -- list2
+ end
+
+ defp check_followers_only(%{host: actor_host} = _actor_info, object) do
+ followers_only =
+ instance_list(:followers_only)
+ |> MRF.subdomains_regex()
+
+ object =
+ with true <- MRF.subdomain_match?(followers_only, actor_host),
+ user <- User.get_cached_by_ap_id(object["actor"]) do
+ # Don't use Map.get/3 intentionally, these must not be nil
+ fixed_to = object["to"] || []
+ fixed_cc = object["cc"] || []
+
+ to = FollowingRelationship.followers_ap_ids(user, fixed_to)
+ cc = FollowingRelationship.followers_ap_ids(user, fixed_cc)
+
+ object
+ |> Map.put("to", intersection([user.follower_address | to], fixed_to))
+ |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc))
+ else
+ _ -> object
+ end
+
+ {:ok, object}
+ end
+
+ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
+ report_removal =
+ instance_list(:report_removal)
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(report_removal, actor_host) do
+ {:reject, "[SimplePolicy] host in report_removal list"}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp check_report_removal(_actor_info, object), do: {:ok, object}
+
+ defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
+ avatar_removal =
+ instance_list(:avatar_removal)
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(avatar_removal, actor_host) do
+ {:ok, Map.delete(object, "icon")}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp check_avatar_removal(_actor_info, object), do: {:ok, object}
+
+ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
+ banner_removal =
+ instance_list(:banner_removal)
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(banner_removal, actor_host) do
+ {:ok, Map.delete(object, "image")}
+ else
+ {:ok, object}
+ end
+ end
+
+ defp check_banner_removal(_actor_info, object), do: {:ok, object}
+
+ defp extract_context_uri(%{"conversation" => "tag:" <> rest}) do
+ rest
+ |> String.split(",", parts: 2, trim: true)
+ |> hd()
+ |> case do
+ nil -> nil
+ hostname -> URI.parse("//" <> hostname)
+ end
+ end
+
+ defp extract_context_uri(%{"context" => "http" <> _ = context}), do: URI.parse(context)
+
+ defp extract_context_uri(_), do: nil
+
+ defp check_context(activity) do
+ uri = extract_context_uri(activity)
+
+ with {:uri, true} <- {:uri, Kernel.match?(%URI{}, uri)},
+ {:ok, _} <- check_accept(uri),
+ {:ok, _} <- check_reject(uri) do
+ {:ok, activity}
+ else
+ # Can't check.
+ {:uri, false} -> {:ok, activity}
+ {:reject, nil} -> {:reject, "[SimplePolicy]"}
+ {:reject, _} = e -> e
+ _ -> {:reject, "[SimplePolicy]"}
+ end
+ end
+
+ defp check_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}} = activity) do
+ with {:ok, _} <- filter(in_reply_to) do
+ {:ok, activity}
+ end
+ end
+
+ defp check_reply_to(activity), do: {:ok, activity}
+
+ defp maybe_check_thread(activity) do
+ if Config.get([:mrf_simple, :handle_threads], true) do
+ with {:ok, _} <- check_context(activity),
+ {:ok, _} <- check_reply_to(activity) do
+ {:ok, activity}
+ end
+ else
+ {:ok, activity}
+ end
+ end
+
+ defp check_object(%{"object" => object} = activity) do
+ with {:ok, _object} <- filter(object) do
+ {:ok, activity}
+ end
+ end
+
+ defp check_object(object), do: {:ok, object}
+
+ defp instance_list(config_key) do
+ Config.get([:mrf_simple, config_key])
+ |> MRF.instance_list_from_tuples()
+ end
+
+ @impl true
+ def filter(%{"type" => "Delete", "actor" => actor} = object) do
+ %{host: actor_host} = URI.parse(actor)
+
+ reject_deletes =
+ instance_list(:reject_deletes)
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(reject_deletes, actor_host) do
+ {:reject, "[SimplePolicy] host in reject_deletes list"}
+ else
+ {:ok, object}
+ end
+ end
+