From f6c543a82a71610df87191b24934ffd6eacbe0ab Mon Sep 17 00:00:00 2001 From: Roman Chvanikov Date: Fri, 31 Jul 2020 12:28:38 +0300 Subject: Static FE --- config/config.exs | 2 - config/description.exs | 15 -- lib/pleroma/frontend.ex | 3 +- lib/pleroma/plugs/static_fe_plug.ex | 18 +- .../web/controller/frontend/static_controller.ex | 160 +++++++++++++++++ lib/pleroma/web/controller/frontend_controller.ex | 4 + lib/pleroma/web/feed/user_controller.ex | 2 + lib/pleroma/web/ostatus/ostatus_controller.ex | 2 + lib/pleroma/web/router.ex | 1 - lib/pleroma/web/static_fe/static_fe_controller.ex | 182 ------------------- lib/pleroma/web/static_fe/static_fe_view.ex | 38 ---- .../templates/frontend/static/_attachment.html.eex | 8 + .../web/templates/frontend/static/_notice.html.eex | 41 +++++ .../templates/frontend/static/_user_card.html.eex | 11 ++ .../frontend/static/conversation.html.eex | 11 ++ .../web/templates/frontend/static/error.html.eex | 7 + .../web/templates/frontend/static/profile.html.eex | 31 ++++ .../static_fe/static_fe/_attachment.html.eex | 8 - .../templates/static_fe/static_fe/_notice.html.eex | 41 ----- .../static_fe/static_fe/_user_card.html.eex | 11 -- .../static_fe/static_fe/conversation.html.eex | 11 -- .../templates/static_fe/static_fe/error.html.eex | 7 - .../templates/static_fe/static_fe/profile.html.eex | 31 ---- lib/pleroma/web/views/frontend/static_view.ex | 38 ++++ test/frontend_test.exs | 4 +- .../frontend/static_controller_test.exs | 192 +++++++++++++++++++++ test/web/static_fe/static_fe_controller_test.exs | 192 --------------------- 27 files changed, 522 insertions(+), 549 deletions(-) create mode 100644 lib/pleroma/web/controller/frontend/static_controller.ex delete mode 100644 lib/pleroma/web/static_fe/static_fe_controller.ex delete mode 100644 lib/pleroma/web/static_fe/static_fe_view.ex create mode 100644 lib/pleroma/web/templates/frontend/static/_attachment.html.eex create mode 100644 lib/pleroma/web/templates/frontend/static/_notice.html.eex create mode 100644 lib/pleroma/web/templates/frontend/static/_user_card.html.eex create mode 100644 lib/pleroma/web/templates/frontend/static/conversation.html.eex create mode 100644 lib/pleroma/web/templates/frontend/static/error.html.eex create mode 100644 lib/pleroma/web/templates/frontend/static/profile.html.eex delete mode 100644 lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex delete mode 100644 lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex delete mode 100644 lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex delete mode 100644 lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex delete mode 100644 lib/pleroma/web/templates/static_fe/static_fe/error.html.eex delete mode 100644 lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex create mode 100644 lib/pleroma/web/views/frontend/static_view.ex create mode 100644 test/web/controllers/frontend/static_controller_test.exs delete mode 100644 test/web/static_fe/static_fe_controller_test.exs diff --git a/config/config.exs b/config/config.exs index 857e0afbb..dbb1622aa 100644 --- a/config/config.exs +++ b/config/config.exs @@ -646,8 +646,6 @@ config :pleroma, Pleroma.ActivityExpiration, enabled: true config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true -config :pleroma, :static_fe, enabled: false - # Example of frontend configuration # This example will make us serve the primary frontend from the # frontends directory within your `:pleroma, :instance, static_dir`. diff --git a/config/description.exs b/config/description.exs index 11fbe0d78..277348d42 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3151,21 +3151,6 @@ config :pleroma, :config_description, [ } ] }, - %{ - group: :pleroma, - key: :static_fe, - label: "Static FE", - type: :group, - description: - "Render profiles and posts using server-generated HTML that is viewable without using JavaScript", - children: [ - %{ - key: :enabled, - type: :boolean, - description: "Enables the rendering of static HTML. Default: disabled." - } - ] - }, %{ group: :pleroma, key: :feed, diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex index f6aa3629f..9aa27cbeb 100644 --- a/lib/pleroma/frontend.ex +++ b/lib/pleroma/frontend.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Frontend do def get_config(:primary) do primary_fe_config = Pleroma.Config.get([:frontends, :primary], %{"name" => "pleroma"}) + static_enabled? = Pleroma.Config.get([:frontends, :static], false) {config, controller} = if primary_fe_config["name"] == "none" do @@ -21,7 +22,7 @@ defmodule Pleroma.Frontend do ])} end - %{"config" => config, "controller" => controller} + %{"config" => config, "controller" => controller, "static" => static_enabled?} end @spec file_path(String.t(), frontend_kind()) :: {:ok, String.t()} | {:error, String.t()} diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/plugs/static_fe_plug.ex index 143665c71..ea8d5ab0c 100644 --- a/lib/pleroma/plugs/static_fe_plug.ex +++ b/lib/pleroma/plugs/static_fe_plug.ex @@ -4,23 +4,25 @@ defmodule Pleroma.Plugs.StaticFEPlug do import Plug.Conn - alias Pleroma.Web.StaticFE.StaticFEController def init(options), do: options - def call(conn, _) do - if enabled?() and requires_html?(conn) do + def call(%{private: %{frontend: %{"static" => true}}} = conn, _) do + action = Phoenix.Controller.action_name(conn) + + if requires_html?(conn) and has_action?(action) do conn - |> StaticFEController.call(:show) + |> Pleroma.Web.FrontendController.call(action) |> halt() else conn end end - defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false) + def call(conn, _), do: conn - defp requires_html?(conn) do - Phoenix.Controller.get_format(conn) == "html" - end + defp requires_html?(conn), do: Phoenix.Controller.get_format(conn) == "html" + + defp has_action?(action), + do: function_exported?(Pleroma.Web.Frontend.StaticController, action, 2) end diff --git a/lib/pleroma/web/controller/frontend/static_controller.ex b/lib/pleroma/web/controller/frontend/static_controller.ex new file mode 100644 index 000000000..947134968 --- /dev/null +++ b/lib/pleroma/web/controller/frontend/static_controller.ex @@ -0,0 +1,160 @@ +defmodule Pleroma.Web.Frontend.StaticController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.Metadata + + plug(:put_layout, :static_fe) + + plug(Pleroma.Plugs.EnsureAuthenticatedPlug, + unless_func: &Pleroma.Web.FederatingPlug.federating?/1 + ) + + @page_keys ["max_id", "min_id", "limit", "since_id", "order"] + + def object(conn, %{"uuid" => _uuid}) do + url = url(conn) <> conn.request_path + + case Activity.get_create_by_object_ap_id_with_object(url) do + %Activity{} = activity -> + to = o_status_path(Pleroma.Web.Endpoint, :notice, activity) + redirect(conn, to: to) + + _ -> + not_found(conn, "Post not found.") + end + end + + def notice(conn, %{"id" => notice_id}) do + with %Activity{local: true} = activity <- + Activity.get_by_id_with_object(notice_id), + true <- Visibility.is_public?(activity.object), + %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do + meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user}) + + timeline = + activity.object.data["context"] + |> ActivityPub.fetch_activities_for_context(%{}) + |> Enum.reverse() + |> Enum.map(&represent(&1, &1.object.id == activity.object.id)) + + render(conn, "conversation.html", %{activities: timeline, meta: meta}) + else + %Activity{object: %Object{data: data}} -> + conn + |> put_status(:found) + |> redirect(external: data["url"] || data["external_url"] || data["id"]) + + _ -> + not_found(conn, "Post not found.") + end + end + + def feed_redirect(conn, %{"nickname" => username_or_id} = params) do + case User.get_cached_by_nickname_or_id(username_or_id) do + %User{} = user -> + meta = Metadata.build_tags(%{user: user}) + + params = + params + |> Map.take(@page_keys) + |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end) + + timeline = + user + |> ActivityPub.fetch_user_activities(nil, params) + |> Enum.map(&represent/1) + + prev_page_id = + (params["min_id"] || params["max_id"]) && + List.first(timeline) && List.first(timeline).id + + next_page_id = List.last(timeline) && List.last(timeline).id + + render(conn, "profile.html", %{ + user: User.sanitize_html(user), + timeline: timeline, + prev_page_id: prev_page_id, + next_page_id: next_page_id, + meta: meta + }) + + _ -> + not_found(conn, "User not found.") + end + end + + def activity(conn, %{"uuid" => _uuid}) do + url = url(conn) <> conn.request_path + + case Activity.get_by_ap_id(url) do + %Activity{} = activity -> + to = o_status_path(Pleroma.Web.Endpoint, :notice, activity) + redirect(conn, to: to) + + _ -> + not_found(conn, "Post not found.") + end + end + + defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), + do: name + + defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary), + do: summary + + defp get_title(_), do: nil + + defp not_found(conn, message) do + conn + |> put_status(404) + |> render("error.html", %{message: message, meta: ""}) + end + + defp get_counts(%Activity{} = activity) do + %Object{data: data} = Object.normalize(activity) + + %{ + likes: data["like_count"] || 0, + replies: data["repliesCount"] || 0, + announces: data["announcement_count"] || 0 + } + end + + defp represent(%Activity{} = activity), do: represent(activity, false) + + defp represent(%Activity{object: %Object{data: data}} = activity, selected) do + {:ok, user} = User.get_or_fetch(activity.object.data["actor"]) + + link = + if user.local do + o_status_url(Pleroma.Web.Endpoint, :notice, activity) + else + data["url"] || data["external_url"] || data["id"] + end + + content = + if data["content"] do + data["content"] + |> Pleroma.HTML.filter_tags() + |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{})) + end + + %{ + user: User.sanitize_html(user), + title: get_title(activity.object), + content: content, + attachment: data["attachment"], + link: link, + published: data["published"], + sensitive: data["sensitive"], + selected: selected, + counts: get_counts(activity), + id: activity.id + } + end +end diff --git a/lib/pleroma/web/controller/frontend_controller.ex b/lib/pleroma/web/controller/frontend_controller.ex index ce04934fe..c77fcc6e4 100644 --- a/lib/pleroma/web/controller/frontend_controller.ex +++ b/lib/pleroma/web/controller/frontend_controller.ex @@ -120,6 +120,10 @@ defmodule Pleroma.Web.FrontendController do {controller, action} = cond do + fe_config["static"] && + function_exported?(Pleroma.Web.Frontend.StaticController, action_name, 2) -> + {Pleroma.Web.Frontend.StaticController, action_name} + function_exported?(fe_config["controller"], action_name, 2) -> {fe_config["controller"], action_name} diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index 7118c087c..4a1f1cfce 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -12,6 +12,8 @@ defmodule Pleroma.Web.Feed.UserController do plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) + plug(Pleroma.Plugs.StaticFEPlug) + action_fallback(:errors) def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index a1b8a3adc..2ded7eac3 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -29,6 +29,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do when action in [:object, :activity, :notice] ) + plug(Pleroma.Plugs.StaticFEPlug) + action_fallback(:errors) def object(%{assigns: %{format: format}} = conn, _params) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 1e3ecf298..1755d0082 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -548,7 +548,6 @@ defmodule Pleroma.Web.Router do pipeline :ostatus do plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"]) - plug(Pleroma.Plugs.StaticFEPlug) end pipeline :oembed do diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex deleted file mode 100644 index a7a891b13..000000000 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ /dev/null @@ -1,182 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.StaticFE.StaticFEController do - use Pleroma.Web, :controller - - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.Metadata - alias Pleroma.Web.Router.Helpers - - plug(:put_layout, :static_fe) - plug(:put_view, Pleroma.Web.StaticFE.StaticFEView) - plug(:assign_id) - - plug(Pleroma.Plugs.EnsureAuthenticatedPlug, - unless_func: &Pleroma.Web.FederatingPlug.federating?/1 - ) - - @page_keys ["max_id", "min_id", "limit", "since_id", "order"] - - defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), - do: name - - defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary), - do: summary - - defp get_title(_), do: nil - - defp not_found(conn, message) do - conn - |> put_status(404) - |> render("error.html", %{message: message, meta: ""}) - end - - defp get_counts(%Activity{} = activity) do - %Object{data: data} = Object.normalize(activity) - - %{ - likes: data["like_count"] || 0, - replies: data["repliesCount"] || 0, - announces: data["announcement_count"] || 0 - } - end - - defp represent(%Activity{} = activity), do: represent(activity, false) - - defp represent(%Activity{object: %Object{data: data}} = activity, selected) do - {:ok, user} = User.get_or_fetch(activity.object.data["actor"]) - - link = - case user.local do - true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) - _ -> data["url"] || data["external_url"] || data["id"] - end - - content = - if data["content"] do - data["content"] - |> Pleroma.HTML.filter_tags() - |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{})) - else - nil - end - - %{ - user: User.sanitize_html(user), - title: get_title(activity.object), - content: content, - attachment: data["attachment"], - link: link, - published: data["published"], - sensitive: data["sensitive"], - selected: selected, - counts: get_counts(activity), - id: activity.id - } - end - - def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do - with %Activity{local: true} = activity <- - Activity.get_by_id_with_object(notice_id), - true <- Visibility.is_public?(activity.object), - %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do - meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user}) - - timeline = - activity.object.data["context"] - |> ActivityPub.fetch_activities_for_context(%{}) - |> Enum.reverse() - |> Enum.map(&represent(&1, &1.object.id == activity.object.id)) - - render(conn, "conversation.html", %{activities: timeline, meta: meta}) - else - %Activity{object: %Object{data: data}} -> - conn - |> put_status(:found) - |> redirect(external: data["url"] || data["external_url"] || data["id"]) - - _ -> - not_found(conn, "Post not found.") - end - end - - def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do - case User.get_cached_by_nickname_or_id(username_or_id) do - %User{} = user -> - meta = Metadata.build_tags(%{user: user}) - - params = - params - |> Map.take(@page_keys) - |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end) - - timeline = - user - |> ActivityPub.fetch_user_activities(nil, params) - |> Enum.map(&represent/1) - - prev_page_id = - (params["min_id"] || params["max_id"]) && - List.first(timeline) && List.first(timeline).id - - next_page_id = List.last(timeline) && List.last(timeline).id - - render(conn, "profile.html", %{ - user: User.sanitize_html(user), - timeline: timeline, - prev_page_id: prev_page_id, - next_page_id: next_page_id, - meta: meta - }) - - _ -> - not_found(conn, "User not found.") - end - end - - def show(%{assigns: %{object_id: _}} = conn, _params) do - url = Helpers.url(conn) <> conn.request_path - - case Activity.get_create_by_object_ap_id_with_object(url) do - %Activity{} = activity -> - to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) - redirect(conn, to: to) - - _ -> - not_found(conn, "Post not found.") - end - end - - def show(%{assigns: %{activity_id: _}} = conn, _params) do - url = Helpers.url(conn) <> conn.request_path - - case Activity.get_by_ap_id(url) do - %Activity{} = activity -> - to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) - redirect(conn, to: to) - - _ -> - not_found(conn, "Post not found.") - end - end - - defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), - do: assign(conn, :notice_id, notice_id) - - defp assign_id(%{path_info: ["users", user_id]} = conn, _opts), - do: assign(conn, :username_or_id, user_id) - - defp assign_id(%{path_info: ["objects", object_id]} = conn, _opts), - do: assign(conn, :object_id, object_id) - - defp assign_id(%{path_info: ["activities", activity_id]} = conn, _opts), - do: assign(conn, :activity_id, activity_id) - - defp assign_id(conn, _opts), do: conn -end diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex deleted file mode 100644 index b3d1d1ec8..000000000 --- a/lib/pleroma/web/static_fe/static_fe_view.ex +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.StaticFE.StaticFEView do - use Pleroma.Web, :view - - alias Calendar.Strftime - alias Pleroma.Emoji.Formatter - alias Pleroma.User - alias Pleroma.Web.Endpoint - alias Pleroma.Web.Gettext - alias Pleroma.Web.MediaProxy - alias Pleroma.Web.Metadata.Utils - alias Pleroma.Web.Router.Helpers - - use Phoenix.HTML - - @media_types ["image", "audio", "video"] - - def fetch_media_type(%{"mediaType" => mediaType}) do - Utils.fetch_media_type(@media_types, mediaType) - end - - def format_date(date) do - {:ok, date, _} = DateTime.from_iso8601(date) - Strftime.strftime!(date, "%Y/%m/%d %l:%M:%S %p UTC") - end - - def instance_name, do: Pleroma.Config.get([:instance, :name], "Pleroma") - - def open_content? do - Pleroma.Config.get( - [:frontend_configurations, :collapse_message_with_subjects], - true - ) - end -end diff --git a/lib/pleroma/web/templates/frontend/static/_attachment.html.eex b/lib/pleroma/web/templates/frontend/static/_attachment.html.eex new file mode 100644 index 000000000..4853e7f4b --- /dev/null +++ b/lib/pleroma/web/templates/frontend/static/_attachment.html.eex @@ -0,0 +1,8 @@ +<%= case @mediaType do %> +<% "audio" -> %> + +<% "video" -> %> + +<% _ -> %> +<%= @name %> +<% end %> diff --git a/lib/pleroma/web/templates/frontend/static/_notice.html.eex b/lib/pleroma/web/templates/frontend/static/_notice.html.eex new file mode 100644 index 000000000..df0244795 --- /dev/null +++ b/lib/pleroma/web/templates/frontend/static/_notice.html.eex @@ -0,0 +1,41 @@ +
id="selected" <% end %>> +

+ + + +

+ <%= render("_user_card.html", %{user: @user}) %> +
+ <%= if @title != "" do %> +
open<% end %>> + <%= raw @title %> +
<%= raw @content %>
+
+ <% else %> +
<%= raw @content %>
+ <% end %> + <%= for %{"name" => name, "url" => [url | _]} <- @attachment do %> + <%= if @sensitive do %> +
+ <%= Gettext.gettext("sensitive media") %> +
+ <%= render("_attachment.html", %{name: name, url: url["href"], + mediaType: fetch_media_type(url)}) %> +
+
+ <% else %> + <%= render("_attachment.html", %{name: name, url: url["href"], + mediaType: fetch_media_type(url)}) %> + <% end %> + <% end %> +
+ <%= if @selected do %> +
+
<%= Gettext.gettext("replies") %>
<%= @counts.replies %>
+
<%= Gettext.gettext("announces") %>
<%= @counts.announces %>
+
<%= Gettext.gettext("likes") %>
<%= @counts.likes %>
+
+ <% end %> +
diff --git a/lib/pleroma/web/templates/frontend/static/_user_card.html.eex b/lib/pleroma/web/templates/frontend/static/_user_card.html.eex new file mode 100644 index 000000000..977b894d3 --- /dev/null +++ b/lib/pleroma/web/templates/frontend/static/_user_card.html.eex @@ -0,0 +1,11 @@ + diff --git a/lib/pleroma/web/templates/frontend/static/conversation.html.eex b/lib/pleroma/web/templates/frontend/static/conversation.html.eex new file mode 100644 index 000000000..2acd84828 --- /dev/null +++ b/lib/pleroma/web/templates/frontend/static/conversation.html.eex @@ -0,0 +1,11 @@ +
+

<%= link instance_name(), to: "/" %>

+
+ +
+
+ <%= for activity <- @activities do %> + <%= render("_notice.html", activity) %> + <% end %> +
+
diff --git a/lib/pleroma/web/templates/frontend/static/error.html.eex b/lib/pleroma/web/templates/frontend/static/error.html.eex new file mode 100644 index 000000000..d98a1eba7 --- /dev/null +++ b/lib/pleroma/web/templates/frontend/static/error.html.eex @@ -0,0 +1,7 @@ +
+

<%= gettext("Oops") %>

+
+ +
+

<%= @message %>

+
diff --git a/lib/pleroma/web/templates/frontend/static/profile.html.eex b/lib/pleroma/web/templates/frontend/static/profile.html.eex new file mode 100644 index 000000000..3191bf450 --- /dev/null +++ b/lib/pleroma/web/templates/frontend/static/profile.html.eex @@ -0,0 +1,31 @@ +
+

<%= link instance_name(), to: "/" %>

+ +

+
+ + + +
+ <%= raw Formatter.emojify(@user.name, @user.emoji) %> | + <%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %> +

+

<%= raw @user.bio %>

+
+ +
+
+ <%= for activity <- @timeline do %> + <%= render("_notice.html", Map.put(activity, :selected, false)) %> + <% end %> +

+ <%= if @prev_page_id do %> + <%= link "«", to: "?min_id=" <> @prev_page_id %> + <% end %> + <%= if @prev_page_id && @next_page_id, do: " | " %> + <%= if @next_page_id do %> + <%= link "»", to: "?max_id=" <> @next_page_id %> + <% end %> +

+
+
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex deleted file mode 100644 index 4853e7f4b..000000000 --- a/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex +++ /dev/null @@ -1,8 +0,0 @@ -<%= case @mediaType do %> -<% "audio" -> %> - -<% "video" -> %> - -<% _ -> %> -<%= @name %> -<% end %> diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex deleted file mode 100644 index df0244795..000000000 --- a/lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex +++ /dev/null @@ -1,41 +0,0 @@ -
id="selected" <% end %>> -

- - - -

- <%= render("_user_card.html", %{user: @user}) %> -
- <%= if @title != "" do %> -
open<% end %>> - <%= raw @title %> -
<%= raw @content %>
-
- <% else %> -
<%= raw @content %>
- <% end %> - <%= for %{"name" => name, "url" => [url | _]} <- @attachment do %> - <%= if @sensitive do %> -
- <%= Gettext.gettext("sensitive media") %> -
- <%= render("_attachment.html", %{name: name, url: url["href"], - mediaType: fetch_media_type(url)}) %> -
-
- <% else %> - <%= render("_attachment.html", %{name: name, url: url["href"], - mediaType: fetch_media_type(url)}) %> - <% end %> - <% end %> -
- <%= if @selected do %> -
-
<%= Gettext.gettext("replies") %>
<%= @counts.replies %>
-
<%= Gettext.gettext("announces") %>
<%= @counts.announces %>
-
<%= Gettext.gettext("likes") %>
<%= @counts.likes %>
-
- <% end %> -
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex deleted file mode 100644 index 977b894d3..000000000 --- a/lib/pleroma/web/templates/static_fe/static_fe/_user_card.html.eex +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex deleted file mode 100644 index 2acd84828..000000000 --- a/lib/pleroma/web/templates/static_fe/static_fe/conversation.html.eex +++ /dev/null @@ -1,11 +0,0 @@ -
-

<%= link instance_name(), to: "/" %>

-
- -
-
- <%= for activity <- @activities do %> - <%= render("_notice.html", activity) %> - <% end %> -
-
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/error.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/error.html.eex deleted file mode 100644 index d98a1eba7..000000000 --- a/lib/pleroma/web/templates/static_fe/static_fe/error.html.eex +++ /dev/null @@ -1,7 +0,0 @@ -
-

<%= gettext("Oops") %>

-
- -
-

<%= @message %>

-
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex deleted file mode 100644 index 3191bf450..000000000 --- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex +++ /dev/null @@ -1,31 +0,0 @@ -
-

<%= link instance_name(), to: "/" %>

- -

-
- - - -
- <%= raw Formatter.emojify(@user.name, @user.emoji) %> | - <%= link "@#{@user.nickname}@#{Endpoint.host()}", to: (@user.uri || @user.ap_id) %> -

-

<%= raw @user.bio %>

-
- -
-
- <%= for activity <- @timeline do %> - <%= render("_notice.html", Map.put(activity, :selected, false)) %> - <% end %> -

- <%= if @prev_page_id do %> - <%= link "«", to: "?min_id=" <> @prev_page_id %> - <% end %> - <%= if @prev_page_id && @next_page_id, do: " | " %> - <%= if @next_page_id do %> - <%= link "»", to: "?max_id=" <> @next_page_id %> - <% end %> -

-
-
diff --git a/lib/pleroma/web/views/frontend/static_view.ex b/lib/pleroma/web/views/frontend/static_view.ex new file mode 100644 index 000000000..7263e45db --- /dev/null +++ b/lib/pleroma/web/views/frontend/static_view.ex @@ -0,0 +1,38 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Frontend.StaticView do + use Pleroma.Web, :view + + alias Calendar.Strftime + alias Pleroma.Emoji.Formatter + alias Pleroma.User + alias Pleroma.Web.Endpoint + alias Pleroma.Web.Gettext + alias Pleroma.Web.MediaProxy + alias Pleroma.Web.Metadata.Utils + alias Pleroma.Web.Router.Helpers + + use Phoenix.HTML + + @media_types ["image", "audio", "video"] + + def fetch_media_type(%{"mediaType" => mediaType}) do + Utils.fetch_media_type(@media_types, mediaType) + end + + def format_date(date) do + {:ok, date, _} = DateTime.from_iso8601(date) + Strftime.strftime!(date, "%Y/%m/%d %l:%M:%S %p UTC") + end + + def instance_name, do: Pleroma.Config.get([:instance, :name], "Pleroma") + + def open_content? do + Pleroma.Config.get( + [:frontend_configurations, :collapse_message_with_subjects], + true + ) + end +end diff --git a/test/frontend_test.exs b/test/frontend_test.exs index b5f04d10a..ddd4b5ee0 100644 --- a/test/frontend_test.exs +++ b/test/frontend_test.exs @@ -10,11 +10,12 @@ defmodule Pleroma.FrontendTest do test "Primary config" do config = %{"name" => "monsta", "ref" => "pika"} - clear_config([:frontends, :primary], config) + clear_config(:frontends, %{primary: config, static: true}) fe_config = Pleroma.Frontend.get_config() assert fe_config["config"] == config assert fe_config["controller"] == Pleroma.Web.Frontend.MonstaController + assert fe_config["static"] == true end test "Headless" do @@ -25,6 +26,7 @@ defmodule Pleroma.FrontendTest do fe_config = Pleroma.Frontend.get_config() assert fe_config["config"] == %{} assert fe_config["controller"] == Pleroma.Web.Frontend.HeadlessController + assert fe_config["static"] == false end end diff --git a/test/web/controllers/frontend/static_controller_test.exs b/test/web/controllers/frontend/static_controller_test.exs new file mode 100644 index 000000000..b36ae2b28 --- /dev/null +++ b/test/web/controllers/frontend/static_controller_test.exs @@ -0,0 +1,192 @@ +defmodule Pleroma.Web.Frontend.StaticControllerTest do + use Pleroma.Web.ConnCase + + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + setup_all do: clear_config([:frontends, :static], true) + setup do: clear_config([:instance, :federating], true) + + setup %{conn: conn} do + conn = put_req_header(conn, "accept", "text/html") + user = insert(:user) + + %{conn: conn, user: user} + end + + describe "user profile html" do + test "just the profile as HTML", %{conn: conn, user: user} do + conn = get(conn, "/users/#{user.nickname}") + + assert html_response(conn, 200) =~ user.nickname + end + + test "404 when user not found", %{conn: conn} do + conn = get(conn, "/users/limpopo") + + assert html_response(conn, 404) =~ "not found" + end + + test "profile does not include private messages", %{conn: conn, user: user} do + CommonAPI.post(user, %{status: "public"}) + CommonAPI.post(user, %{status: "private", visibility: "private"}) + + conn = get(conn, "/users/#{user.nickname}") + + html = html_response(conn, 200) + + assert html =~ ">public<" + refute html =~ ">private<" + end + + test "pagination", %{conn: conn, user: user} do + Enum.map(1..30, fn i -> CommonAPI.post(user, %{status: "test#{i}"}) end) + + conn = get(conn, "/users/#{user.nickname}") + + html = html_response(conn, 200) + + assert html =~ ">test30<" + assert html =~ ">test11<" + refute html =~ ">test10<" + refute html =~ ">test1<" + end + + test "pagination, page 2", %{conn: conn, user: user} do + activities = Enum.map(1..30, fn i -> CommonAPI.post(user, %{status: "test#{i}"}) end) + {:ok, a11} = Enum.at(activities, 11) + + conn = get(conn, "/users/#{user.nickname}?max_id=#{a11.id}") + + html = html_response(conn, 200) + + assert html =~ ">test1<" + assert html =~ ">test10<" + refute html =~ ">test20<" + refute html =~ ">test29<" + end + + test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do + ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user) + end + end + + describe "notice html" do + test "single notice page", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) + + conn = get(conn, "/notice/#{activity.id}") + + html = html_response(conn, 200) + assert html =~ "
" + assert html =~ user.nickname + assert html =~ "testing a thing!" + end + + test "redirects to json if requested", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) + + conn = + conn + |> put_req_header( + "accept", + "Accept: application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", text/html" + ) + |> get("/notice/#{activity.id}") + + assert redirected_to(conn, 302) =~ activity.data["object"] + end + + test "filters HTML tags", %{conn: conn} do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: ""}) + + conn = + conn + |> put_req_header("accept", "text/html") + |> get("/notice/#{activity.id}") + + html = html_response(conn, 200) + assert html =~ ~s[<script>alert('xss')</script>] + end + + test "shows the whole thread", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{status: "space: the final frontier"}) + + CommonAPI.post(user, %{ + status: "these are the voyages or something", + in_reply_to_status_id: activity.id + }) + + conn = get(conn, "/notice/#{activity.id}") + + html = html_response(conn, 200) + assert html =~ "the final frontier" + assert html =~ "voyages" + end + + test "redirect by AP object ID", %{conn: conn, user: user} do + {:ok, %Activity{data: %{"object" => object_url}}} = + CommonAPI.post(user, %{status: "beam me up"}) + + conn = get(conn, URI.parse(object_url).path) + + assert html_response(conn, 302) =~ "redirected" + end + + test "redirect by activity ID", %{conn: conn, user: user} do + {:ok, %Activity{data: %{"id" => id}}} = + CommonAPI.post(user, %{status: "I'm a doctor, not a devops!"}) + + conn = get(conn, URI.parse(id).path) + + assert html_response(conn, 302) =~ "redirected" + end + + test "404 when notice not found", %{conn: conn} do + conn = get(conn, "/notice/88c9c317") + + assert html_response(conn, 404) =~ "not found" + end + + test "404 for private status", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{status: "don't show me!", visibility: "private"}) + + conn = get(conn, "/notice/#{activity.id}") + + assert html_response(conn, 404) =~ "not found" + end + + test "302 for remote cached status", %{conn: conn, user: user} do + message = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "to" => user.follower_address, + "cc" => "https://www.w3.org/ns/activitystreams#Public", + "type" => "Create", + "object" => %{ + "content" => "blah blah blah", + "type" => "Note", + "attributedTo" => user.ap_id, + "inReplyTo" => nil + }, + "actor" => user.ap_id + } + + assert {:ok, activity} = Transmogrifier.handle_incoming(message) + + conn = get(conn, "/notice/#{activity.id}") + + assert html_response(conn, 302) =~ "redirected" + end + + test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do + {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) + + ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user) + end + end +end diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/web/static_fe/static_fe_controller_test.exs deleted file mode 100644 index 1598bf675..000000000 --- a/test/web/static_fe/static_fe_controller_test.exs +++ /dev/null @@ -1,192 +0,0 @@ -defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do - use Pleroma.Web.ConnCase - - alias Pleroma.Activity - alias Pleroma.Config - alias Pleroma.Web.ActivityPub.Transmogrifier - alias Pleroma.Web.CommonAPI - - import Pleroma.Factory - - setup_all do: clear_config([:static_fe, :enabled], true) - setup do: clear_config([:instance, :federating], true) - - setup %{conn: conn} do - conn = put_req_header(conn, "accept", "text/html") - user = insert(:user) - - %{conn: conn, user: user} - end - - describe "user profile html" do - test "just the profile as HTML", %{conn: conn, user: user} do - conn = get(conn, "/users/#{user.nickname}") - - assert html_response(conn, 200) =~ user.nickname - end - - test "404 when user not found", %{conn: conn} do - conn = get(conn, "/users/limpopo") - - assert html_response(conn, 404) =~ "not found" - end - - test "profile does not include private messages", %{conn: conn, user: user} do - CommonAPI.post(user, %{status: "public"}) - CommonAPI.post(user, %{status: "private", visibility: "private"}) - - conn = get(conn, "/users/#{user.nickname}") - - html = html_response(conn, 200) - - assert html =~ ">public<" - refute html =~ ">private<" - end - - test "pagination", %{conn: conn, user: user} do - Enum.map(1..30, fn i -> CommonAPI.post(user, %{status: "test#{i}"}) end) - - conn = get(conn, "/users/#{user.nickname}") - - html = html_response(conn, 200) - - assert html =~ ">test30<" - assert html =~ ">test11<" - refute html =~ ">test10<" - refute html =~ ">test1<" - end - - test "pagination, page 2", %{conn: conn, user: user} do - activities = Enum.map(1..30, fn i -> CommonAPI.post(user, %{status: "test#{i}"}) end) - {:ok, a11} = Enum.at(activities, 11) - - conn = get(conn, "/users/#{user.nickname}?max_id=#{a11.id}") - - html = html_response(conn, 200) - - assert html =~ ">test1<" - assert html =~ ">test10<" - refute html =~ ">test20<" - refute html =~ ">test29<" - end - - test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do - ensure_federating_or_authenticated(conn, "/users/#{user.nickname}", user) - end - end - - describe "notice html" do - test "single notice page", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) - - conn = get(conn, "/notice/#{activity.id}") - - html = html_response(conn, 200) - assert html =~ "
" - assert html =~ user.nickname - assert html =~ "testing a thing!" - end - - test "redirects to json if requested", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) - - conn = - conn - |> put_req_header( - "accept", - "Accept: application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", text/html" - ) - |> get("/notice/#{activity.id}") - - assert redirected_to(conn, 302) =~ activity.data["object"] - end - - test "filters HTML tags", %{conn: conn} do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{status: ""}) - - conn = - conn - |> put_req_header("accept", "text/html") - |> get("/notice/#{activity.id}") - - html = html_response(conn, 200) - assert html =~ ~s[<script>alert('xss')</script>] - end - - test "shows the whole thread", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{status: "space: the final frontier"}) - - CommonAPI.post(user, %{ - status: "these are the voyages or something", - in_reply_to_status_id: activity.id - }) - - conn = get(conn, "/notice/#{activity.id}") - - html = html_response(conn, 200) - assert html =~ "the final frontier" - assert html =~ "voyages" - end - - test "redirect by AP object ID", %{conn: conn, user: user} do - {:ok, %Activity{data: %{"object" => object_url}}} = - CommonAPI.post(user, %{status: "beam me up"}) - - conn = get(conn, URI.parse(object_url).path) - - assert html_response(conn, 302) =~ "redirected" - end - - test "redirect by activity ID", %{conn: conn, user: user} do - {:ok, %Activity{data: %{"id" => id}}} = - CommonAPI.post(user, %{status: "I'm a doctor, not a devops!"}) - - conn = get(conn, URI.parse(id).path) - - assert html_response(conn, 302) =~ "redirected" - end - - test "404 when notice not found", %{conn: conn} do - conn = get(conn, "/notice/88c9c317") - - assert html_response(conn, 404) =~ "not found" - end - - test "404 for private status", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{status: "don't show me!", visibility: "private"}) - - conn = get(conn, "/notice/#{activity.id}") - - assert html_response(conn, 404) =~ "not found" - end - - test "302 for remote cached status", %{conn: conn, user: user} do - message = %{ - "@context" => "https://www.w3.org/ns/activitystreams", - "to" => user.follower_address, - "cc" => "https://www.w3.org/ns/activitystreams#Public", - "type" => "Create", - "object" => %{ - "content" => "blah blah blah", - "type" => "Note", - "attributedTo" => user.ap_id, - "inReplyTo" => nil - }, - "actor" => user.ap_id - } - - assert {:ok, activity} = Transmogrifier.handle_incoming(message) - - conn = get(conn, "/notice/#{activity.id}") - - assert html_response(conn, 302) =~ "redirected" - end - - test "it requires authentication if instance is NOT federating", %{conn: conn, user: user} do - {:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"}) - - ensure_federating_or_authenticated(conn, "/notice/#{activity.id}", user) - end - end -end -- cgit v1.2.3