Release/2.0.7 See merge request pleroma/secteam/pleroma!8tags/v2.0.7
@@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. | |||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
## [2.0.7] - 2020-06-13 | |||
### Security | |||
- Fix potential DoSes exploiting atom leaks in rich media parser and the `UserAllowListPolicy` MRF policy | |||
### Fixed | |||
- CSP: not allowing images/media from every host when mediaproxy is disabled | |||
- CSP: not adding mediaproxy base url to image/media hosts | |||
- StaticFE missing the CSS file | |||
### Upgrade notes | |||
1. Restart Pleroma | |||
## [2.0.6] - 2020-06-09 | |||
### Security | |||
@@ -1483,14 +1483,12 @@ config :pleroma, :config_description, [ | |||
# %{ | |||
# group: :pleroma, | |||
# key: :mrf_user_allowlist, | |||
# type: :group, | |||
# type: :map, | |||
# description: | |||
# "The keys in this section are the domain names that the policy should apply to." <> | |||
# " Each key should be assigned a list of users that should be allowed through by their ActivityPub ID", | |||
# children: [ | |||
# ["example.org": ["https://example.org/users/admin"]], | |||
# suggestions: [ | |||
# ["example.org": ["https://example.org/users/admin"]] | |||
# %{"example.org" => ["https://example.org/users/admin"]} | |||
# ] | |||
# ] | |||
# }, | |||
@@ -133,8 +133,9 @@ their ActivityPub ID. | |||
An example: | |||
```elixir | |||
config :pleroma, :mrf_user_allowlist, | |||
"example.org": ["https://example.org/users/admin"] | |||
config :pleroma, :mrf_user_allowlist, %{ | |||
"example.org" => ["https://example.org/users/admin"] | |||
} | |||
``` | |||
#### :mrf_object_age | |||
@@ -4,9 +4,10 @@ | |||
defmodule Pleroma.Config.DeprecationWarnings do | |||
require Logger | |||
alias Pleroma.Config | |||
def check_hellthread_threshold do | |||
if Pleroma.Config.get([:mrf_hellthread, :threshold]) do | |||
if Config.get([:mrf_hellthread, :threshold]) do | |||
Logger.warn(""" | |||
!!!DEPRECATION WARNING!!! | |||
You are using the old configuration mechanism for the hellthread filter. Please check config.md. | |||
@@ -14,7 +15,29 @@ defmodule Pleroma.Config.DeprecationWarnings do | |||
end | |||
end | |||
def mrf_user_allowlist do | |||
config = Config.get(:mrf_user_allowlist) | |||
if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do | |||
rewritten = | |||
Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc -> | |||
Map.put(acc, to_string(k), v) | |||
end) | |||
Config.put(:mrf_user_allowlist, rewritten) | |||
Logger.error(""" | |||
!!!DEPRECATION WARNING!!! | |||
As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format. | |||
Pleroma 2.1 will remove support for the old format. Please change your configuration to match this: | |||
config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)} | |||
""") | |||
end | |||
end | |||
def warn do | |||
check_hellthread_threshold() | |||
mrf_user_allowlist() | |||
end | |||
end |
@@ -20,4 +20,9 @@ defmodule Pleroma.Constants do | |||
"deleted_activity_id" | |||
] | |||
) | |||
const(static_only_files, | |||
do: | |||
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) | |||
) | |||
end |
@@ -75,7 +75,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
sources = get_proxy_and_attachment_sources() | |||
{[img_src, sources], [media_src, sources]} | |||
else | |||
{img_src, media_src} | |||
{[img_src, " https:"], [media_src, " https:"]} | |||
end | |||
connect_src = ["connect-src 'self' ", static_url, ?\s, websocket_url] | |||
@@ -113,6 +113,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
add_source(acc, host) | |||
end) | |||
media_proxy_base_url = | |||
if Config.get([:media_proxy, :base_url]), | |||
do: URI.parse(Config.get([:media_proxy, :base_url])).host | |||
upload_base_url = | |||
if Config.get([Pleroma.Upload, :base_url]), | |||
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host | |||
@@ -122,6 +126,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host | |||
[] | |||
|> add_source(media_proxy_base_url) | |||
|> add_source(upload_base_url) | |||
|> add_source(s3_endpoint) | |||
|> add_source(media_proxy_whitelist) | |||
@@ -3,6 +3,8 @@ | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Plugs.InstanceStatic do | |||
require Pleroma.Constants | |||
@moduledoc """ | |||
This is a shim to call `Plug.Static` but with runtime `from` configuration. | |||
@@ -21,9 +23,6 @@ defmodule Pleroma.Plugs.InstanceStatic do | |||
end | |||
end | |||
@only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js | |||
sw-pleroma.js) | |||
def init(opts) do | |||
opts | |||
|> Keyword.put(:from, "__unconfigured_instance_static_plug") | |||
@@ -31,7 +30,7 @@ defmodule Pleroma.Plugs.InstanceStatic do | |||
|> Plug.Static.init() | |||
end | |||
for only <- @only do | |||
for only <- Pleroma.Constants.static_only_files() do | |||
at = Plug.Router.Utils.split("/") | |||
def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do | |||
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do | |||
allow_list = | |||
Config.get( | |||
[:mrf_user_allowlist, String.to_atom(actor_info.host)], | |||
[:mrf_user_allowlist, actor_info.host], | |||
[] | |||
) | |||
@@ -5,6 +5,8 @@ | |||
defmodule Pleroma.Web.Endpoint do | |||
use Phoenix.Endpoint, otp_app: :pleroma | |||
require Pleroma.Constants | |||
socket("/socket", Pleroma.Web.UserSocket) | |||
plug(Pleroma.Plugs.SetLocalePlug) | |||
@@ -34,8 +36,7 @@ defmodule Pleroma.Web.Endpoint do | |||
Plug.Static, | |||
at: "/", | |||
from: :pleroma, | |||
only: | |||
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc), | |||
only: Pleroma.Constants.static_only_files(), | |||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength | |||
gzip: true, | |||
cache_control_for_etags: @static_cache_control, | |||
@@ -307,8 +307,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
page_url_data = URI.parse(page_url) | |||
page_url_data = | |||
if rich_media[:url] != nil do | |||
URI.merge(page_url_data, URI.parse(rich_media[:url])) | |||
if is_binary(rich_media["url"]) do | |||
URI.merge(page_url_data, URI.parse(rich_media["url"])) | |||
else | |||
page_url_data | |||
end | |||
@@ -316,11 +316,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
page_url = page_url_data |> to_string | |||
image_url = | |||
if rich_media[:image] != nil do | |||
URI.merge(page_url_data, URI.parse(rich_media[:image])) | |||
if is_binary(rich_media["image"]) do | |||
URI.merge(page_url_data, URI.parse(rich_media["image"])) | |||
|> to_string | |||
else | |||
nil | |||
end | |||
%{ | |||
@@ -329,8 +327,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
provider_url: page_url_data.scheme <> "://" <> page_url_data.host, | |||
url: page_url, | |||
image: image_url |> MediaProxy.url(), | |||
title: rich_media[:title] || "", | |||
description: rich_media[:description] || "", | |||
title: rich_media["title"] || "", | |||
description: rich_media["description"] || "", | |||
pleroma: %{ | |||
opengraph: rich_media | |||
} | |||
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do | |||
alias Pleroma.Object | |||
alias Pleroma.Web.RichMedia.Parser | |||
@spec validate_page_url(any()) :: :ok | :error | |||
@spec validate_page_url(URI.t() | binary()) :: :ok | :error | |||
defp validate_page_url(page_url) when is_binary(page_url) do | |||
validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld] | |||
@@ -18,8 +18,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do | |||
|> parse_uri(page_url) | |||
end | |||
defp validate_page_url(%URI{host: host, scheme: scheme, authority: authority}) | |||
when scheme == "https" and not is_nil(authority) do | |||
defp validate_page_url(%URI{host: host, scheme: "https", authority: authority}) | |||
when is_binary(authority) do | |||
cond do | |||
host in Config.get([:rich_media, :ignore_hosts], []) -> | |||
:error | |||
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.RichMedia.Parser do | |||
html | |||
|> parse_html() | |||
|> maybe_parse() | |||
|> Map.put(:url, url) | |||
|> Map.put("url", url) | |||
|> clean_parsed_data() | |||
|> check_parsed_data() | |||
rescue | |||
@@ -103,8 +103,8 @@ defmodule Pleroma.Web.RichMedia.Parser do | |||
end) | |||
end | |||
defp check_parsed_data(%{title: title} = data) | |||
when is_binary(title) and byte_size(title) > 0 do | |||
defp check_parsed_data(%{"title" => title} = data) | |||
when is_binary(title) and title != "" do | |||
{:ok, data} | |||
end | |||
@@ -115,11 +115,7 @@ defmodule Pleroma.Web.RichMedia.Parser do | |||
defp clean_parsed_data(data) do | |||
data | |||
|> Enum.reject(fn {key, val} -> | |||
with {:ok, _} <- Jason.encode(%{key => val}) do | |||
false | |||
else | |||
_ -> true | |||
end | |||
not match?({:ok, _}, Jason.encode(%{key => val})) | |||
end) | |||
|> Map.new() | |||
end | |||
@@ -29,19 +29,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do | |||
{_tag, attributes, _children} = html_node | |||
data = | |||
Enum.into(attributes, %{}, fn {name, value} -> | |||
Map.new(attributes, fn {name, value} -> | |||
{name, String.trim_leading(value, "#{prefix}:")} | |||
end) | |||
%{String.to_atom(data[key_name]) => data[value_name]} | |||
%{data[key_name] => data[value_name]} | |||
end | |||
defp maybe_put_title(%{title: _} = meta, _), do: meta | |||
defp maybe_put_title(%{"title" => _} = meta, _), do: meta | |||
defp maybe_put_title(meta, html) when meta != %{} do | |||
case get_page_title(html) do | |||
"" -> meta | |||
title -> Map.put_new(meta, :title, title) | |||
title -> Map.put_new(meta, "title", title) | |||
end | |||
end | |||
@@ -5,7 +5,7 @@ | |||
defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do | |||
def parse(html, _data) do | |||
with elements = [_ | _] <- get_discovery_data(html), | |||
{:ok, oembed_url} <- get_oembed_url(elements), | |||
oembed_url when is_binary(oembed_url) <- get_oembed_url(elements), | |||
{:ok, oembed_data} <- get_oembed_data(oembed_url) do | |||
{:ok, oembed_data} | |||
else | |||
@@ -17,19 +17,13 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do | |||
html |> Floki.find("link[type='application/json+oembed']") | |||
end | |||
defp get_oembed_url(nodes) do | |||
{"link", attributes, _children} = nodes |> hd() | |||
{:ok, Enum.into(attributes, %{})["href"]} | |||
defp get_oembed_url([{"link", attributes, _children} | _]) do | |||
Enum.find_value(attributes, fn {k, v} -> if k == "href", do: v end) | |||
end | |||
defp get_oembed_data(url) do | |||
{:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media]) | |||
{:ok, data} = Jason.decode(json) | |||
data = data |> Map.new(fn {k, v} -> {String.to_atom(k), v} end) | |||
{:ok, data} | |||
with {:ok, %Tesla.Env{body: json}} <- Pleroma.HTTP.get(url, [], adapter: [pool: :media]) do | |||
Jason.decode(json) | |||
end | |||
end | |||
end |
@@ -5,7 +5,7 @@ | |||
<meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" /> | |||
<title><%= Pleroma.Config.get([:instance, :name]) %></title> | |||
<%= Phoenix.HTML.raw(assigns[:meta] || "") %> | |||
<link rel="stylesheet" href="/static/static-fe.css"> | |||
<link rel="stylesheet" href="/static-fe/static-fe.css"> | |||
</head> | |||
<body> | |||
<div class="container"> | |||
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do | |||
def project do | |||
[ | |||
app: :pleroma, | |||
version: version("2.0.6"), | |||
version: version("2.0.7"), | |||
elixir: "~> 1.8", | |||
elixirc_paths: elixirc_paths(Mix.env()), | |||
compilers: [:phoenix, :gettext] ++ Mix.compilers(), | |||
@@ -0,0 +1,183 @@ | |||
body { | |||
background-color: #282c37; | |||
font-family: sans-serif; | |||
color: white; | |||
} | |||
main { | |||
margin: 50px auto; | |||
max-width: 960px; | |||
padding: 40px; | |||
background-color: #313543; | |||
border-radius: 4px; | |||
} | |||
header { | |||
margin: 50px auto; | |||
max-width: 960px; | |||
padding: 40px; | |||
background-color: #313543; | |||
border-radius: 4px; | |||
} | |||
.activity { | |||
border-radius: 4px; | |||
padding: 1em; | |||
padding-bottom: 2em; | |||
margin-bottom: 1em; | |||
} | |||
.avatar { | |||
cursor: pointer; | |||
} | |||
.avatar img { | |||
float: left; | |||
border-radius: 4px; | |||
margin-right: 4px; | |||
} | |||
.activity-content img, video, audio { | |||
padding: 1em; | |||
max-width: 800px; | |||
max-height: 800px; | |||
} | |||
#selected { | |||
background-color: #1b2735; | |||
} | |||
.counts dt, .counts dd { | |||
float: left; | |||
margin-left: 1em; | |||
} | |||
a { | |||
color: white; | |||
} | |||
.h-card { | |||
min-height: 48px; | |||
margin-bottom: 8px; | |||
} | |||
header a, .h-card a { | |||
text-decoration: none; | |||
} | |||
header a:hover, .h-card a:hover { | |||
text-decoration: underline; | |||
} | |||
.display-name { | |||
padding-top: 4px; | |||
display: block; | |||
text-overflow: ellipsis; | |||
overflow: hidden; | |||
color: white; | |||
} | |||
/* keep emoji from being hilariously huge */ | |||
.display-name img { | |||
max-height: 1em; | |||
} | |||
.display-name .nickname { | |||
padding-top: 4px; | |||
display: block; | |||
} | |||
.nickname:hover { | |||
text-decoration: none; | |||
} | |||
.pull-right { | |||
float: right; | |||
} | |||
.collapse { | |||
margin: 0; | |||
width: auto; | |||
} | |||
h1 { | |||
margin: 0; | |||
} | |||
h2 { | |||
color: #9baec8; | |||
font-weight: normal; | |||
font-size: 20px; | |||
margin-bottom: 40px; | |||
} | |||
form { | |||
width: 100%; | |||
} | |||
input { | |||
box-sizing: border-box; | |||
width: 100%; | |||
padding: 10px; | |||
margin-top: 20px; | |||
background-color: rgba(0,0,0,.1); | |||
color: white; | |||
border: 0; | |||
border-bottom: 2px solid #9baec8; | |||
font-size: 14px; | |||
} | |||
input:focus { | |||
border-bottom: 2px solid #4b8ed8; | |||
} | |||
input[type="checkbox"] { | |||
width: auto; | |||
} | |||
button { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: white; | |||
background-color: #419bdd; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
margin-top: 30px; | |||
text-transform: uppercase; | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
.alert-danger { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: #D8000C; | |||
background-color: #FFD2D2; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
margin-top: 20px; | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
.alert-info { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: #00529B; | |||
background-color: #BDE5F8; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
margin-top: 20px; | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
img.emoji { | |||
width: 32px; | |||
height: 32px; | |||
padding: 0; | |||
vertical-align: middle; | |||
} |
@@ -17,14 +17,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do | |||
test "pass filter if allow list isn't empty and user in allow list" do | |||
actor = insert(:user) | |||
Pleroma.Config.put([:mrf_user_allowlist, :localhost], [actor.ap_id, "test-ap-id"]) | |||
Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => [actor.ap_id, "test-ap-id"]}) | |||
message = %{"actor" => actor.ap_id} | |||
assert UserAllowListPolicy.filter(message) == {:ok, message} | |||
end | |||
test "rejected if allow list isn't empty and user not in allow list" do | |||
actor = insert(:user) | |||
Pleroma.Config.put([:mrf_user_allowlist, :localhost], ["test-ap-id"]) | |||
Pleroma.Config.put([:mrf_user_allowlist], %{"localhost" => ["test-ap-id"]}) | |||
message = %{"actor" => actor.ap_id} | |||
assert UserAllowListPolicy.filter(message) == {:reject, nil} | |||
end | |||
@@ -60,19 +60,19 @@ defmodule Pleroma.Web.RichMedia.ParserTest do | |||
test "doesn't just add a title" do | |||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") == | |||
{:error, | |||
"Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"} | |||
"Found metadata was invalid or incomplete: %{\"url\" => \"http://example.com/non-ogp\"}"} | |||
end | |||
test "parses ogp" do | |||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp") == | |||
{:ok, | |||
%{ | |||
image: "http://ia.media-imdb.com/images/rock.jpg", | |||
title: "The Rock", | |||
description: | |||
"image" => "http://ia.media-imdb.com/images/rock.jpg", | |||
"title" => "The Rock", | |||
"description" => | |||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", | |||
type: "video.movie", | |||
url: "http://example.com/ogp" | |||
"type" => "video.movie", | |||
"url" => "http://example.com/ogp" | |||
}} | |||
end | |||
@@ -80,12 +80,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do | |||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/ogp-missing-title") == | |||
{:ok, | |||
%{ | |||
image: "http://ia.media-imdb.com/images/rock.jpg", | |||
title: "The Rock (1996)", | |||
description: | |||
"image" => "http://ia.media-imdb.com/images/rock.jpg", | |||
"title" => "The Rock (1996)", | |||
"description" => | |||
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", | |||
type: "video.movie", | |||
url: "http://example.com/ogp-missing-title" | |||
"type" => "video.movie", | |||
"url" => "http://example.com/ogp-missing-title" | |||
}} | |||
end | |||
@@ -93,12 +93,12 @@ defmodule Pleroma.Web.RichMedia.ParserTest do | |||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/twitter-card") == | |||
{:ok, | |||
%{ | |||
card: "summary", | |||
site: "@flickr", | |||
image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg", | |||
title: "Small Island Developing States Photo Submission", | |||
description: "View the album on Flickr.", | |||
url: "http://example.com/twitter-card" | |||
"card" => "summary", | |||
"site" => "@flickr", | |||
"image" => "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg", | |||
"title" => "Small Island Developing States Photo Submission", | |||
"description" => "View the album on Flickr.", | |||
"url" => "http://example.com/twitter-card" | |||
}} | |||
end | |||
@@ -106,27 +106,28 @@ defmodule Pleroma.Web.RichMedia.ParserTest do | |||
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/oembed") == | |||
{:ok, | |||
%{ | |||
author_name: "bees", | |||
author_url: "https://www.flickr.com/photos/bees/", | |||
cache_age: 3600, | |||
flickr_type: "photo", | |||
height: "768", | |||
html: | |||
"author_name" => "bees", | |||
"author_url" => "https://www.flickr.com/photos/bees/", | |||
"cache_age" => 3600, | |||
"flickr_type" => "photo", | |||
"height" => "768", | |||
"html" => | |||
"<a data-flickr-embed=\"true\" href=\"https://www.flickr.com/photos/bees/2362225867/\" title=\"Bacon Lollys by bees, on Flickr\"><img src=\"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg\" width=\"1024\" height=\"768\" alt=\"Bacon Lollys\"></a><script async src=\"https://embedr.flickr.com/assets/client-code.js\" charset=\"utf-8\"></script>", | |||
license: "All Rights Reserved", | |||
license_id: 0, | |||
provider_name: "Flickr", | |||
provider_url: "https://www.flickr.com/", | |||
thumbnail_height: 150, | |||
thumbnail_url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg", | |||
thumbnail_width: 150, | |||
title: "Bacon Lollys", | |||
type: "photo", | |||
url: "http://example.com/oembed", | |||
version: "1.0", | |||
web_page: "https://www.flickr.com/photos/bees/2362225867/", | |||
web_page_short_url: "https://flic.kr/p/4AK2sc", | |||
width: "1024" | |||
"license" => "All Rights Reserved", | |||
"license_id" => 0, | |||
"provider_name" => "Flickr", | |||
"provider_url" => "https://www.flickr.com/", | |||
"thumbnail_height" => 150, | |||
"thumbnail_url" => | |||
"https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_q.jpg", | |||
"thumbnail_width" => 150, | |||
"title" => "Bacon Lollys", | |||
"type" => "photo", | |||
"url" => "http://example.com/oembed", | |||
"version" => "1.0", | |||
"web_page" => "https://www.flickr.com/photos/bees/2362225867/", | |||
"web_page_short_url" => "https://flic.kr/p/4AK2sc", | |||
"width" => "1024" | |||
}} | |||
end | |||
@@ -19,11 +19,11 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do | |||
assert TwitterCard.parse(html, %{}) == | |||
{:ok, | |||
%{ | |||
"app:id:googleplay": "com.nytimes.android", | |||
"app:name:googleplay": "NYTimes", | |||
"app:url:googleplay": "nytimes://reader/id/100000006583622", | |||
site: nil, | |||
title: | |||
"app:id:googleplay" => "com.nytimes.android", | |||
"app:name:googleplay" => "NYTimes", | |||
"app:url:googleplay" => "nytimes://reader/id/100000006583622", | |||
"site" => nil, | |||
"title" => | |||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times" | |||
}} | |||
end | |||
@@ -36,15 +36,15 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do | |||
assert TwitterCard.parse(html, %{}) == | |||
{:ok, | |||
%{ | |||
card: "summary_large_image", | |||
description: | |||
"card" => "summary_large_image", | |||
"description" => | |||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.", | |||
image: | |||
"image" => | |||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg", | |||
"image:alt": "", | |||
title: | |||
"image:alt" => "", | |||
"title" => | |||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.", | |||
url: | |||
"url" => | |||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" | |||
}} | |||
end | |||
@@ -57,19 +57,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do | |||
assert TwitterCard.parse(html, %{}) == | |||
{:ok, | |||
%{ | |||
"app:id:googleplay": "com.nytimes.android", | |||
"app:name:googleplay": "NYTimes", | |||
"app:url:googleplay": "nytimes://reader/id/100000006583622", | |||
card: "summary_large_image", | |||
description: | |||
"app:id:googleplay" => "com.nytimes.android", | |||
"app:name:googleplay" => "NYTimes", | |||
"app:url:googleplay" => "nytimes://reader/id/100000006583622", | |||
"card" => "summary_large_image", | |||
"description" => | |||
"With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.", | |||
image: | |||
"image" => | |||
"https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg", | |||
"image:alt": "", | |||
site: nil, | |||
title: | |||
"image:alt" => "", | |||
"site" => nil, | |||
"title" => | |||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.", | |||
url: | |||
"url" => | |||
"https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" | |||
}} | |||
end | |||
@@ -86,11 +86,11 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do | |||
assert TwitterCard.parse(html, %{}) == | |||
{:ok, | |||
%{ | |||
site: "@atlasobscura", | |||
title: | |||
"site" => "@atlasobscura", | |||
"title" => | |||
"The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura", | |||
card: "summary_large_image", | |||
image: image_path | |||
"card" => "summary_large_image", | |||
"image" => image_path | |||
}} | |||
end | |||
@@ -102,12 +102,12 @@ defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do | |||
assert TwitterCard.parse(html, %{}) == | |||
{:ok, | |||
%{ | |||
site: nil, | |||
title: | |||
"site" => nil, | |||
"title" => | |||
"She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times", | |||
"app:id:googleplay": "com.nytimes.android", | |||
"app:name:googleplay": "NYTimes", | |||
"app:url:googleplay": "nytimes://reader/id/100000006583622" | |||
"app:id:googleplay" => "com.nytimes.android", | |||
"app:name:googleplay" => "NYTimes", | |||
"app:url:googleplay" => "nytimes://reader/id/100000006583622" | |||
}} | |||
end | |||
end |