Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
6 KB
Referenced Files
None
Subscribers
None
diff --git a/changelog.d/c2s-hardening.security b/changelog.d/c2s-hardening.security
index 1e756add8..134d674bd 100644
--- a/changelog.d/c2s-hardening.security
+++ b/changelog.d/c2s-hardening.security
@@ -6,3 +6,4 @@ Hold new local polls to the poll limits and reject preset vote counts
Reject local replies to and quotes of posts the author cannot see or quote
Local posts and edits only accept own uploads as attachments, within the attachment and description limits, and emoji with shortcode names
C2S: the server sets publish time, context and other server-owned fields of new objects, scrubs their HTML and counts contentMap toward the character limit
+Feeds scrub post content like the Mastodon API and no longer fail on posts without a publish time
diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex
index b409b1923..68f6ed69c 100644
--- a/lib/pleroma/web/feed/feed_view.ex
+++ b/lib/pleroma/web/feed/feed_view.ex
@@ -27,11 +27,36 @@ defmodule Pleroma.Web.Feed.FeedView do
%{
activity: activity,
object: object,
- data: Map.get(object, :data),
+ data: object |> Map.get(:data) |> prepare_data(activity),
actor: actor
}
end
+ # Feeds show the content like the Mastodon API does: scrubbed. Objects
+ # without a publish time fall back to the time the activity was stored.
+ defp prepare_data(%{} = data, activity) do
+ data
+ |> Map.update("content", nil, &scrub_content(&1, activity))
+ |> Map.put_new_lazy("published", fn -> stored_at(activity) end)
+ |> Map.update!("published", &(&1 || stored_at(activity)))
+ end
+
+ defp prepare_data(data, _activity), do: data
+
+ defp scrub_content(content, activity) when is_binary(content) do
+ Pleroma.Activity.HTML.get_cached_scrubbed_html_for_activity(
+ content,
+ Pleroma.HTML.get_scrubbers(),
+ activity,
+ "feed:content"
+ )
+ end
+
+ defp scrub_content(content, _activity), do: content
+
+ defp stored_at(%{inserted_at: %NaiveDateTime{} = inserted_at}),
+ do: inserted_at |> DateTime.from_naive!("Etc/UTC") |> DateTime.to_iso8601()
+
def most_recent_update(activities) do
with %{updated_at: updated_at} <- List.first(activities) do
to_rfc3339(updated_at)
diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
index 35e29ed8c..a50205a5e 100644
--- a/test/pleroma/web/feed/tag_controller_test.exs
+++ b/test/pleroma/web/feed/tag_controller_test.exs
@@ -189,9 +189,15 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
obj1 = Object.normalize(activity1, fetch: false)
obj2 = Object.normalize(activity2, fetch: false)
+ scrubbed_content = fn object ->
+ %{"content" => Pleroma.HTML.filter_tags(object.data["content"])}
+ |> FeedView.activity_content()
+ |> HtmlEntities.decode()
+ end
+
assert xpath(xml, ~x"//channel/item/description/text()"sl) == [
- HtmlEntities.decode(FeedView.activity_content(obj2.data)),
- HtmlEntities.decode(FeedView.activity_content(obj1.data))
+ scrubbed_content.(obj2),
+ scrubbed_content.(obj1)
]
response =
@@ -243,6 +249,21 @@ defmodule Pleroma.Web.Feed.TagControllerTest do
assert {:ok, _datetime, 0} = DateTime.from_iso8601(updated)
end
+ test "survives a post without a publish time", %{conn: conn} do
+ {:ok, activity} = CommonAPI.post(insert(:user), %{status: "no date #shared"})
+ object = Object.normalize(activity, fetch: false)
+
+ object
+ |> Ecto.Changeset.change(data: Map.delete(object.data, "published"))
+ |> Pleroma.Repo.update!()
+
+ for format <- ["rss", "atom"] do
+ assert conn
+ |> get(tag_feed_path(conn, :feed, "shared.#{format}"))
+ |> response(200) =~ "no date"
+ end
+ end
+
test "escapes Atom IDs and tag feed self links", %{conn: conn} do
user = insert(:user, local: false)
object_id = "https://remote.example/object?first=one&second=two"
diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
index 92ae75996..73b43b8e6 100644
--- a/test/pleroma/web/feed/user_controller_test.exs
+++ b/test/pleroma/web/feed/user_controller_test.exs
@@ -89,7 +89,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
assert activity_titles == [~c"Won't, didn'...", ~c"2hu", ~c"2hu & as"]
assert SweetXml.xpath(xml, ~x"//entry/link[@rel='self']/@href"sl) == []
assert length(SweetXml.xpath(xml, ~x"//entry/link[@rel='alternate']/@href"sl)) == 3
- assert resp =~ FeedView.escape(object.data["content"])
+ assert resp =~ FeedView.escape(Pleroma.HTML.filter_tags(object.data["content"]))
assert resp =~ FeedView.escape(object.data["summary"])
assert resp =~ FeedView.escape(object.data["context"])
@@ -120,7 +120,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
|> SweetXml.xpath(~x"//item/title/text()"l)
assert activity_titles == [~c"Won't, didn'...", ~c"2hu", ~c"2hu & as"]
- assert resp =~ FeedView.escape(object.data["content"])
+ assert resp =~ FeedView.escape(Pleroma.HTML.filter_tags(object.data["content"]))
assert resp =~ FeedView.escape(object.data["summary"])
assert resp =~ FeedView.escape(object.data["context"])
@@ -179,6 +179,31 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
end)
end
+ test "scrubs content and survives a missing publish time", %{
+ conn: conn,
+ object: object,
+ user: user
+ } do
+ data =
+ object.data
+ |> Map.put("content", "<script>alert('feed')</script><b>kept</b>")
+ |> Map.delete("published")
+
+ object
+ |> Ecto.Changeset.change(data: data)
+ |> Pleroma.Repo.update!()
+
+ for format <- ["atom", "rss"] do
+ response =
+ conn
+ |> get("/users/#{user.nickname}/feed.#{format}")
+ |> response(200)
+
+ refute response =~ ~r/(<|&lt;|&amp;lt;)script/
+ assert response =~ "kept"
+ end
+ end
+
test "does not expose sensitive attachments as enclosures", %{
conn: conn,
object: object,
@@ -319,7 +344,7 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
|> SweetXml.xpath(~x"//entry/title/text()"l)
assert activity_titles == [~c"Won't, didn'...", ~c"2hu", ~c"2hu & as"]
- assert resp =~ FeedView.escape(object.data["content"])
+ assert resp =~ FeedView.escape(Pleroma.HTML.filter_tags(object.data["content"]))
assert resp =~ FeedView.escape(object.data["summary"])
assert resp =~ FeedView.escape(object.data["context"])
assert resp =~ encoded_title

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 1, 12:50 AM (17 h, 7 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802002
Default Alt Text
(6 KB)

Event Timeline