Rich Text Redo Branch See merge request pleroma/pleroma!314tags/v0.9.9
@@ -74,7 +74,12 @@ config :pleroma, :instance, | |||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy, | rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy, | ||||
public: true, | public: true, | ||||
quarantined_instances: [], | quarantined_instances: [], | ||||
managed_config: true | |||||
managed_config: true, | |||||
allowed_post_formats: [ | |||||
"text/plain", | |||||
"text/html", | |||||
"text/markdown" | |||||
] | |||||
config :pleroma, :markup, | config :pleroma, :markup, | ||||
# XXX - unfortunately, inline images must be enabled by default right now, because | # XXX - unfortunately, inline images must be enabled by default right now, because | ||||
@@ -98,6 +103,7 @@ config :pleroma, :fe, | |||||
redirect_root_login: "/main/friends", | redirect_root_login: "/main/friends", | ||||
show_instance_panel: true, | show_instance_panel: true, | ||||
scope_options_enabled: false, | scope_options_enabled: false, | ||||
formatting_options_enabled: false, | |||||
collapse_message_with_subject: false | collapse_message_with_subject: false | ||||
config :pleroma, :activitypub, | config :pleroma, :activitypub, | ||||
@@ -192,7 +192,11 @@ defmodule Pleroma.Formatter do | |||||
] | ] | ||||
# TODO: make it use something other than @link_regex | # TODO: make it use something other than @link_regex | ||||
def html_escape(text) do | |||||
def html_escape(text, "text/html") do | |||||
HTML.filter_tags(text) | |||||
end | |||||
def html_escape(text, "text/plain") do | |||||
Regex.split(@link_regex, text, include_captures: true) | Regex.split(@link_regex, text, include_captures: true) | ||||
|> Enum.map_every(2, fn chunk -> | |> Enum.map_every(2, fn chunk -> | ||||
{:safe, part} = Phoenix.HTML.html_escape(chunk) | {:safe, part} = Phoenix.HTML.html_escape(chunk) | ||||
@@ -73,6 +73,11 @@ defmodule Pleroma.Web.CommonAPI do | |||||
def get_visibility(_), do: "public" | def get_visibility(_), do: "public" | ||||
@instance Application.get_env(:pleroma, :instance) | @instance Application.get_env(:pleroma, :instance) | ||||
@allowed_post_formats Keyword.get(@instance, :allowed_post_formats) | |||||
defp get_content_type(content_type) when content_type in @allowed_post_formats, do: content_type | |||||
defp get_content_type(_), do: "text/plain" | |||||
@limit Keyword.get(@instance, :limit) | @limit Keyword.get(@instance, :limit) | ||||
def post(user, %{"status" => status} = data) do | def post(user, %{"status" => status} = data) do | ||||
visibility = get_visibility(data) | visibility = get_visibility(data) | ||||
@@ -85,7 +90,14 @@ defmodule Pleroma.Web.CommonAPI do | |||||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility), | {to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility), | ||||
tags <- Formatter.parse_tags(status, data), | tags <- Formatter.parse_tags(status, data), | ||||
content_html <- | content_html <- | ||||
make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]), | |||||
make_content_html( | |||||
status, | |||||
mentions, | |||||
attachments, | |||||
tags, | |||||
get_content_type(data["content_type"]), | |||||
data["no_attachment_links"] | |||||
), | |||||
context <- make_context(inReplyTo), | context <- make_context(inReplyTo), | ||||
cw <- data["spoiler_text"], | cw <- data["spoiler_text"], | ||||
object <- | object <- | ||||
@@ -63,9 +63,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||||
end | end | ||||
end | end | ||||
def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do | |||||
def make_content_html( | |||||
status, | |||||
mentions, | |||||
attachments, | |||||
tags, | |||||
content_type, | |||||
no_attachment_links \\ false | |||||
) do | |||||
status | status | ||||
|> format_input(mentions, tags) | |||||
|> format_input(mentions, tags, content_type) | |||||
|> maybe_add_attachments(attachments, no_attachment_links) | |> maybe_add_attachments(attachments, no_attachment_links) | ||||
end | end | ||||
@@ -92,9 +99,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||||
Enum.join([text | attachment_text], "<br>") | Enum.join([text | attachment_text], "<br>") | ||||
end | end | ||||
def format_input(text, mentions, tags) do | |||||
def format_input(text, mentions, tags, "text/plain") do | |||||
text | text | ||||
|> Formatter.html_escape() | |||||
|> Formatter.html_escape("text/plain") | |||||
|> String.replace(~r/\r?\n/, "<br>") | |> String.replace(~r/\r?\n/, "<br>") | ||||
|> (&{[], &1}).() | |> (&{[], &1}).() | ||||
|> Formatter.add_links() | |> Formatter.add_links() | ||||
@@ -103,6 +110,25 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||||
|> Formatter.finalize() | |> Formatter.finalize() | ||||
end | end | ||||
def format_input(text, mentions, tags, "text/html") do | |||||
text | |||||
|> Formatter.html_escape("text/html") | |||||
|> String.replace(~r/\r?\n/, "<br>") | |||||
|> (&{[], &1}).() | |||||
|> Formatter.add_user_links(mentions) | |||||
|> Formatter.finalize() | |||||
end | |||||
def format_input(text, mentions, tags, "text/markdown") do | |||||
text | |||||
|> Earmark.as_html!() | |||||
|> Formatter.html_escape("text/html") | |||||
|> String.replace(~r/\r?\n/, "") | |||||
|> (&{[], &1}).() | |||||
|> Formatter.add_user_links(mentions) | |||||
|> Formatter.finalize() | |||||
end | |||||
def add_tag_links(text, tags) do | def add_tag_links(text, tags) do | ||||
tags = | tags = | ||||
tags | tags | ||||
@@ -92,7 +92,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do | |||||
mrf_policies: mrf_policies, | mrf_policies: mrf_policies, | ||||
mrf_simple: mrf_simple, | mrf_simple: mrf_simple, | ||||
quarantined_instances: quarantined | quarantined_instances: quarantined | ||||
} | |||||
}, | |||||
postFormats: Keyword.get(instance, :allowed_post_formats) | |||||
} | } | ||||
} | } | ||||
@@ -176,6 +176,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do | |||||
chatDisabled: !Keyword.get(@instance_chat, :enabled), | chatDisabled: !Keyword.get(@instance_chat, :enabled), | ||||
showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel), | showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel), | ||||
scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled), | scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled), | ||||
formattingOptionsEnabled: Keyword.get(@instance_fe, :formatting_options_enabled), | |||||
collapseMessageWithSubject: Keyword.get(@instance_fe, :collapse_message_with_subject) | collapseMessageWithSubject: Keyword.get(@instance_fe, :collapse_message_with_subject) | ||||
} | } | ||||
@@ -423,7 +423,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||||
{String.trim(name, ":"), url} | {String.trim(name, ":"), url} | ||||
end) | end) | ||||
bio_html = CommonUtils.format_input(bio, mentions, tags) | |||||
bio_html = CommonUtils.format_input(bio, mentions, tags, "text/plain") | |||||
Map.put(params, "bio", bio_html |> Formatter.emojify(emoji)) | Map.put(params, "bio", bio_html |> Formatter.emojify(emoji)) | ||||
else | else | ||||
params | params | ||||
@@ -48,6 +48,7 @@ defmodule Pleroma.Mixfile do | |||||
{:mogrify, "~> 0.6.1"}, | {:mogrify, "~> 0.6.1"}, | ||||
{:ex_aws, "~> 2.0"}, | {:ex_aws, "~> 2.0"}, | ||||
{:ex_aws_s3, "~> 2.0"}, | {:ex_aws_s3, "~> 2.0"}, | ||||
{:earmark, "~> 1.2"}, | |||||
{:ex_machina, "~> 2.2", only: :test}, | {:ex_machina, "~> 2.2", only: :test}, | ||||
{:credo, "~> 0.9.3", only: [:dev, :test]}, | {:credo, "~> 0.9.3", only: [:dev, :test]}, | ||||
{:mock, "~> 0.3.1", only: :test}, | {:mock, "~> 0.3.1", only: :test}, | ||||
@@ -11,6 +11,7 @@ | |||||
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, | "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, | ||||
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, | "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, | ||||
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, | "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, | ||||
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, | |||||
"ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, | "ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, | ||||
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"}, | "eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"}, | ||||
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, | "ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"}, | ||||
@@ -21,4 +21,36 @@ defmodule Pleroma.Web.CommonAPI.Test do | |||||
assert karjalanpiirakka["name"] == ":karjalanpiirakka:" | assert karjalanpiirakka["name"] == ":karjalanpiirakka:" | ||||
end | end | ||||
describe "posting" do | |||||
test "it filters out obviously bad tags when accepting a post as HTML" do | |||||
user = insert(:user) | |||||
post = "<p><b>2hu</b></p><script>alert('xss')</script>" | |||||
{:ok, activity} = | |||||
CommonAPI.post(user, %{ | |||||
"status" => post, | |||||
"content_type" => "text/html" | |||||
}) | |||||
content = activity.data["object"]["content"] | |||||
assert content == "<p><b>2hu</b></p>alert('xss')" | |||||
end | |||||
test "it filters out obviously bad tags when accepting a post as Markdown" do | |||||
user = insert(:user) | |||||
post = "<p><b>2hu</b></p><script>alert('xss')</script>" | |||||
{:ok, activity} = | |||||
CommonAPI.post(user, %{ | |||||
"status" => post, | |||||
"content_type" => "text/markdown" | |||||
}) | |||||
content = activity.data["object"]["content"] | |||||
assert content == "<p><b>2hu</b></p>alert('xss')" | |||||
end | |||||
end | |||||
end | end |