1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.OStatus.OStatusController do
6 use Pleroma.Web, :controller
8 alias Fallback.RedirectController
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.ActivityPubController
14 alias Pleroma.Web.ActivityPub.ObjectView
15 alias Pleroma.Web.ActivityPub.Visibility
16 alias Pleroma.Web.Endpoint
17 alias Pleroma.Web.Federator
18 alias Pleroma.Web.Metadata.PlayerView
19 alias Pleroma.Web.OStatus
20 alias Pleroma.Web.OStatus.ActivityRepresenter
21 alias Pleroma.Web.OStatus.FeedRepresenter
22 alias Pleroma.Web.Router
25 plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
28 Pleroma.Plugs.SetFormatPlug
29 when action in [:feed_redirect, :object, :activity, :notice]
32 action_fallback(:errors)
34 def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
35 with {_, %User{} = user} <-
36 {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
37 RedirectController.redirector_with_meta(conn, %{user: user})
41 def feed_redirect(%{assigns: %{format: format}} = conn, _params)
42 when format in ["json", "activity+json"] do
43 ActivityPubController.call(conn, :user)
46 def feed_redirect(conn, %{"nickname" => nickname}) do
47 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
48 redirect(conn, external: OStatus.feed_path(user))
52 def feed(conn, %{"nickname" => nickname} = params) do
53 with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
55 Map.take(params, ["max_id"])
56 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
59 ActivityPub.fetch_public_activities(query_params)
64 |> FeedRepresenter.to_simple_form(activities, [user])
65 |> :xmerl.export_simple(:xmerl_xml)
69 |> put_resp_content_type("application/atom+xml")
70 |> send_resp(200, response)
74 defp decode_or_retry(body) do
75 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
76 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
80 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
81 doc <- XML.parse_document(decoded),
82 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
83 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
84 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
85 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
91 def salmon_incoming(conn, _) do
92 {:ok, body, _conn} = read_body(conn)
93 {:ok, doc} = decode_or_retry(body)
95 Federator.incoming_doc(doc)
101 def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
102 when format in ["json", "activity+json"] do
103 ActivityPubController.call(conn, :object)
106 def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
107 with id <- o_status_url(conn, :object, uuid),
108 {_, %Activity{} = activity} <-
109 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
110 {_, true} <- {:public?, Visibility.is_public?(activity)},
111 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
113 "html" -> redirect(conn, to: "/notice/#{activity.id}")
114 _ -> represent_activity(conn, nil, activity, user)
117 reason when reason in [{:public?, false}, {:activity, nil}] ->
125 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
126 when format in ["json", "activity+json"] do
127 ActivityPubController.call(conn, :activity)
130 def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
131 with id <- o_status_url(conn, :activity, uuid),
132 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
133 {_, true} <- {:public?, Visibility.is_public?(activity)},
134 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
136 "html" -> redirect(conn, to: "/notice/#{activity.id}")
137 _ -> represent_activity(conn, format, activity, user)
140 reason when reason in [{:public?, false}, {:activity, nil}] ->
148 def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
149 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
150 {_, true} <- {:public?, Visibility.is_public?(activity)},
151 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
153 format == "html" && activity.data["type"] == "Create" ->
154 %Object{} = object = Object.normalize(activity)
156 RedirectController.redirector_with_meta(
159 activity_id: activity.id,
161 url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
167 RedirectController.redirector(conn, nil)
170 represent_activity(conn, format, activity, user)
173 reason when reason in [{:public?, false}, {:activity, nil}] ->
176 |> RedirectController.redirector(nil, 404)
183 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
184 def notice_player(conn, %{"id" => id}) do
185 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
186 true <- Visibility.is_public?(activity),
187 %Object{} = object <- Object.normalize(activity),
188 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
189 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
191 |> put_layout(:metadata_player)
192 |> put_resp_header("x-frame-options", "ALLOW")
194 "content-security-policy",
195 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
197 |> put_view(PlayerView)
198 |> render("player.html", url)
203 |> RedirectController.redirector(nil, 404)
207 defp represent_activity(
210 %Activity{data: %{"type" => "Create"}} = activity,
213 object = Object.normalize(activity)
216 |> put_resp_header("content-type", "application/activity+json")
217 |> json(ObjectView.render("object.json", %{object: object}))
220 defp represent_activity(_conn, "activity+json", _, _) do
224 defp represent_activity(conn, _, activity, user) do
227 |> ActivityRepresenter.to_simple_form(user, true)
228 |> ActivityRepresenter.wrap_with_entry()
229 |> :xmerl.export_simple(:xmerl_xml)
233 |> put_resp_content_type("application/atom+xml")
234 |> send_resp(200, response)
237 def errors(conn, {:error, :not_found}) do
238 render_error(conn, :not_found, "Not found")
241 def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
243 def errors(conn, _) do
244 render_error(conn, :internal_server_error, "Something went wrong")