Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85648404
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
23 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index f0d45293e..b1ea3178d 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -1,257 +1,263 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# 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
plug(:put_layout, :static_fe)
plug(:assign_id)
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
@doc "Renders requested local public activity or public activities of requested user"
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),
{_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
- meta = Metadata.build_tags(%{url: activity.data["id"], object: activity.object, user: user})
+ meta =
+ Metadata.build_tags(%{
+ activity_id: notice_id,
+ url: activity.data["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, tab: tab}} = conn, params) do
with {_, %User{local: true} = user} <-
{:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)},
{_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
meta = Metadata.build_tags(%{user: user})
params =
params
|> Map.take(@page_keys)
|> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
|> Map.put(:limit, 20)
params =
case tab do
"posts" ->
Map.put(params, :exclude_replies, true)
"media" ->
Map.put(params, :only_media, true)
_ ->
params
end
timeline =
case tab do
tab when tab in ["posts", "with_replies", "media"] ->
user
|> ActivityPub.fetch_user_activities(_reading_user = nil, params)
|> Enum.map(&represent/1)
"following" when not user.hide_follows ->
User.get_friends(user)
"followers" when not user.hide_followers ->
User.get_followers(user)
_ ->
[]
end
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
})
else
{_, %User{} = user} ->
conn
|> put_status(:found)
|> redirect(external: user.uri || user.ap_id)
_ ->
not_found(conn, "User not found.")
end
end
def show(%{assigns: %{object_id: _}} = conn, _params) do
url = unverified_url(conn, conn.request_path)
case Activity.get_create_by_object_ap_id_with_object(url) do
%Activity{} = activity ->
to = ~p[/notice/#{activity}]
redirect(conn, to: to)
_ ->
not_found(conn, "Post not found.")
end
end
def show(%{assigns: %{activity_id: _}} = conn, _params) do
url = unverified_url(conn, conn.request_path)
case Activity.get_by_ap_id(url) do
%Activity{} = activity ->
to = ~p[/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, fetch: false)
%{
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 -> ~p[/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
reply_to_user = in_reply_to_user(activity)
total_votes =
if data["oneOf"] do
Enum.sum(for option <- data["oneOf"], do: option["replies"]["totalItems"])
else
0
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,
visibility: Visibility.get_visibility(activity.object),
reply_to: data["inReplyTo"],
reply_to_user: reply_to_user,
edited_at: data["updated"],
poll: data["oneOf"],
total_votes: total_votes
}
end
defp in_reply_to_user(%Activity{object: %Object{data: %{"inReplyTo" => inReplyTo}}} = activity)
when is_binary(inReplyTo) do
in_reply_to_activity = Activity.get_in_reply_to_activity(activity)
if in_reply_to_activity do
in_reply_to_activity
|> Map.get(:actor)
|> User.get_cached_by_ap_id()
else
nil
end
end
defp in_reply_to_user(_), do: nil
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: ["@" <> _nickname, notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: ["@" <> _nickname, "posts", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: [_nickname, "status", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
defp assign_id(%{path_info: ["users", user_id]} = conn, _opts),
do:
conn
|> assign(:username_or_id, user_id)
|> assign(:tab, "posts")
defp assign_id(%{path_info: ["users", user_id, tab]} = conn, _opts),
do:
conn
|> assign(:username_or_id, user_id)
|> assign(:tab, tab)
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/test/pleroma/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs
index 935e44171..79d4d6261 100644
--- a/test/pleroma/web/static_fe/static_fe_controller_test.exs
+++ b/test/pleroma/web/static_fe/static_fe_controller_test.exs
@@ -1,294 +1,449 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
use Pleroma.Web.ConnCase, async: false
alias Pleroma.Activity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
setup_all do: clear_config([:static_fe, :enabled], true)
setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local)
setup %{conn: conn} do
conn = put_req_header(conn, "accept", "text/html")
- user = insert(:user)
- %{conn: conn, user: user}
+ user_avatar_url = "https://example.org/akko.png"
+
+ user =
+ insert(:user,
+ local: true,
+ name: "Akko",
+ nickname: "atsuko",
+ bio: "A believing heart is my magic!",
+ raw_bio: "A believing heart is my magic!",
+ avatar: %{
+ "url" => [
+ %{
+ "href" => user_avatar_url
+ }
+ ]
+ }
+ )
+
+ %{conn: conn, user: user, user_avatar_url: user_avatar_url}
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 =~ "\npublic\n"
refute html =~ "\nprivate\n"
end
test "main page does not include replies", %{conn: conn, user: user} do
{:ok, op} = CommonAPI.post(user, %{status: "beep"})
CommonAPI.post(user, %{status: "boop", in_reply_to_id: op})
conn = get(conn, "/users/#{user.nickname}")
html = html_response(conn, 200)
assert html =~ "\nbeep\n"
refute html =~ "\nboop\n"
end
test "media page only includes posts with attachments", %{conn: conn, user: user} do
file = %Plug.Upload{
content_type: "image/jpeg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
{:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
CommonAPI.post(user, %{status: "virgin text post"})
CommonAPI.post(user, %{status: "chad post with attachment", media_ids: [media_id]})
conn = get(conn, "/users/#{user.nickname}/media")
html = html_response(conn, 200)
assert html =~ "\nchad post with attachment\n"
refute html =~ "\nvirgin text post\n"
end
test "show follower list", %{conn: conn, user: user} do
follower = insert(:user)
CommonAPI.follow(follower, user)
conn = get(conn, "/users/#{user.nickname}/followers")
html = html_response(conn, 200)
assert html =~ "user-card"
end
test "don't show followers if hidden", %{conn: conn, user: user} do
follower = insert(:user)
CommonAPI.follow(follower, user)
{:ok, user} =
user
|> User.update_changeset(%{hide_followers: true})
|> User.update_and_set_cache()
conn = get(conn, "/users/#{user.nickname}/followers")
html = html_response(conn, 200)
refute html =~ "user-card"
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 =~ "\ntest30\n"
assert html =~ "\ntest11\n"
refute html =~ "\ntest10\n"
refute html =~ "\ntest1\n"
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 =~ "\ntest1\n"
assert html =~ "\ntest10\n"
refute html =~ "\ntest20\n"
refute html =~ "\ntest29\n"
end
test "does not require authentication on non-federating instances", %{
conn: conn,
user: user
} do
clear_config([:instance, :federating], false)
conn = get(conn, "/users/#{user.nickname}")
assert html_response(conn, 200) =~ user.nickname
end
test "returns 404 for local user with `restrict_unauthenticated/profiles/local` setting", %{
conn: conn
} do
clear_config([:restrict_unauthenticated, :profiles, :local], true)
local_user = insert(:user, local: true)
conn
|> get("/users/#{local_user.nickname}")
|> html_response(404)
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 =~ "<div class=\"panel conversation\">"
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: "<script>alert('xss')</script>"})
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",
"type" => "Create",
"actor" => user.ap_id,
"object" => %{
"to" => user.follower_address,
"cc" => "https://www.w3.org/ns/activitystreams#Public",
"id" => Utils.generate_object_id(),
"content" => "blah blah blah",
"type" => "Note",
"attributedTo" => user.ap_id
}
}
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
conn = get(conn, "/notice/#{activity.id}")
assert html_response(conn, 302) =~ "redirected"
end
test "does not require authentication on non-federating instances", %{
conn: conn,
user: user
} do
clear_config([:instance, :federating], false)
{:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
conn = get(conn, "/notice/#{activity.id}")
assert html_response(conn, 200) =~ "testing a thing!"
end
test "returns 404 for local public activity with `restrict_unauthenticated/activities/local` setting",
%{conn: conn, user: user} do
clear_config([:restrict_unauthenticated, :activities, :local], true)
{:ok, activity} = CommonAPI.post(user, %{status: "testing a thing!"})
conn
|> get("/notice/#{activity.id}")
|> html_response(404)
end
end
+
+ defp meta_content(metadata_tag) do
+ :proplists.get_value("content", metadata_tag)
+ end
+
+ defp meta_find_og(document, name) do
+ Floki.find(document, "head>meta[property=\"og:" <> name <> "\"]")
+ end
+
+ defp meta_find_twitter(document, name) do
+ Floki.find(document, "head>meta[name=\"twitter:" <> name <> "\"]")
+ end
+
+ # Detailed metadata tests are already done for each builder individually, so just
+ # one check per type of content should suffice to ensure we're calling the providers correctly
+ describe "metadata tags for" do
+ setup do
+ clear_config([Pleroma.Web.Metadata, :providers], [
+ Pleroma.Web.Metadata.Providers.OpenGraph,
+ Pleroma.Web.Metadata.Providers.TwitterCard
+ ])
+ end
+
+ test "user profile", %{conn: conn, user: user, user_avatar_url: user_avatar_url} do
+ conn = get(conn, "/users/#{user.nickname}")
+ html = html_response(conn, 200)
+
+ {:ok, document} = Floki.parse_document(html)
+
+ [{"meta", og_type, _}] = meta_find_og(document, "type")
+ [{"meta", og_title, _}] = meta_find_og(document, "title")
+ [{"meta", og_url, _}] = meta_find_og(document, "url")
+ [{"meta", og_desc, _}] = meta_find_og(document, "description")
+ [{"meta", og_img, _}] = meta_find_og(document, "image")
+ [{"meta", og_imgw, _}] = meta_find_og(document, "image:width")
+ [{"meta", og_imgh, _}] = meta_find_og(document, "image:height")
+
+ [{"meta", tw_card, _}] = meta_find_twitter(document, "card")
+ [{"meta", tw_title, _}] = meta_find_twitter(document, "title")
+ [{"meta", tw_desc, _}] = meta_find_twitter(document, "description")
+ [{"meta", tw_img, _}] = meta_find_twitter(document, "image")
+
+ assert meta_content(og_type) == "article"
+ assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user)
+ assert meta_content(og_url) == user.ap_id
+ assert meta_content(og_desc) == user.bio
+ assert meta_content(og_img) == user_avatar_url
+ assert meta_content(og_imgw) == "150"
+ assert meta_content(og_imgh) == "150"
+
+ assert meta_content(tw_card) == "summary"
+ assert meta_content(tw_title) == meta_content(og_title)
+ assert meta_content(tw_desc) == meta_content(og_desc)
+ assert meta_content(tw_img) == meta_content(og_img)
+ end
+
+ test "text-only post", %{conn: conn, user: user, user_avatar_url: user_avatar_url} do
+ post_text = "How are lessons about magic t h i s boring?!"
+ {:ok, activity} = CommonAPI.post(user, %{status: post_text})
+
+ conn = get(conn, "/notice/#{activity.id}")
+ html = html_response(conn, 200)
+
+ {:ok, document} = Floki.parse_document(html)
+
+ [{"meta", og_type, _}] = meta_find_og(document, "type")
+ [{"meta", og_title, _}] = meta_find_og(document, "title")
+ [{"meta", og_url, _}] = meta_find_og(document, "url")
+ [{"meta", og_desc, _}] = meta_find_og(document, "description")
+ [{"meta", og_img, _}] = meta_find_og(document, "image")
+ [{"meta", og_imgw, _}] = meta_find_og(document, "image:width")
+ [{"meta", og_imgh, _}] = meta_find_og(document, "image:height")
+
+ [{"meta", tw_card, _}] = meta_find_twitter(document, "card")
+ [{"meta", tw_title, _}] = meta_find_twitter(document, "title")
+ [{"meta", tw_desc, _}] = meta_find_twitter(document, "description")
+ [{"meta", tw_img, _}] = meta_find_twitter(document, "image")
+
+ assert meta_content(og_type) == "article"
+ assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user)
+ assert meta_content(og_url) == activity.data["id"]
+ assert meta_content(og_desc) == post_text
+ assert meta_content(og_img) == user_avatar_url
+ assert meta_content(og_imgw) == "150"
+ assert meta_content(og_imgh) == "150"
+
+ assert meta_content(tw_card) == "summary"
+ assert meta_content(tw_title) == meta_content(og_title)
+ assert meta_content(tw_desc) == meta_content(og_desc)
+ assert meta_content(tw_img) == meta_content(og_img)
+ end
+
+ test "post with attachments", %{conn: conn, user: user} do
+ file = %Plug.Upload{
+ content_type: "image/jpeg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ alt_text = "The rarest of all Shiny Chariot cards"
+ {:ok, upload_data} = ActivityPub.upload(file, actor: user.ap_id, description: alt_text)
+
+ %{id: media_id, data: %{"url" => [%{"href" => media_url}]}} = upload_data
+
+ post_text = "Look!"
+ {:ok, activity} = CommonAPI.post(user, %{status: post_text, media_ids: [media_id]})
+
+ conn = get(conn, "/notice/#{activity.id}")
+ html = html_response(conn, 200)
+
+ {:ok, document} = Floki.parse_document(html)
+
+ [{"meta", og_type, _}] = meta_find_og(document, "type")
+ [{"meta", og_title, _}] = meta_find_og(document, "title")
+ [{"meta", og_url, _}] = meta_find_og(document, "url")
+ [{"meta", og_desc, _}] = meta_find_og(document, "description")
+ [{"meta", og_img, _}] = meta_find_og(document, "image")
+ [{"meta", og_alt, _}] = meta_find_og(document, "image:alt")
+
+ [{"meta", tw_card, _}] = meta_find_twitter(document, "card")
+ [{"meta", tw_title, _}] = meta_find_twitter(document, "title")
+ [{"meta", tw_desc, _}] = meta_find_twitter(document, "description")
+ [{"meta", tw_player, _}] = meta_find_twitter(document, "player")
+
+ assert meta_content(og_type) == "article"
+ assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user)
+ assert meta_content(og_url) == activity.data["id"]
+ assert meta_content(og_desc) == post_text
+ assert meta_content(og_img) == media_url
+ assert meta_content(og_alt) == alt_text
+
+ # Audio and video attachments use "player" and have some more metadata
+ assert meta_content(tw_card) == "summary_large_image"
+ assert meta_content(tw_title) == meta_content(og_title)
+ assert meta_content(tw_desc) == meta_content(og_desc)
+ assert meta_content(tw_player) == meta_content(og_img)
+ end
+ end
end
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Aug 28, 10:18 AM (3 h, 41 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1736353
Default Alt Text
(23 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment