# Copyright © 2017-2018 E-MetroTel # Copyright © 2019-2022 Pleroma Authors # SPDX-License-Identifier: MIT defmodule LinkifyTest do use ExUnit.Case, async: true doctest Linkify test "default link" do assert Linkify.link("google.com") == "google.com" end test "default link iodata" do assert Linkify.link_to_iodata("google.com") == [["", "google.com", ""]] end test "default link safe iodata" do assert Linkify.link_safe("google.com") == [ [ {:safe, [""]}, "google.com", {:safe, ""} ] ] end test "does on link existing links" do text = ~s(google.com) assert Linkify.link(text) == text end test "all kinds of links" do text = "hello google.com https://ddg.com user@email.com irc:///mIRC" expected = "hello google.com https://ddg.com user@email.com irc:///mIRC" assert Linkify.link(text, email: true, extra: true ) == expected end test "all kinds of links iodata" do text = "hello google.com https://ddg.com user@email.com irc:///mIRC" expected = [ "hello", " ", ["", "google.com", ""], " ", ["", "https://ddg.com", ""], " ", ["", "user@email.com", ""], " ", ["", "irc:///mIRC", ""] ] assert Linkify.link_to_iodata(text, email: true, extra: true ) == expected end test "class attribute" do assert Linkify.link("google.com", class: "linkified") == "google.com" end test "class attribute iodata" do assert Linkify.link_to_iodata("google.com", class: "linkified") == [ [ "", "google.com", "" ] ] end test "rel attribute" do assert Linkify.link("google.com", rel: "noopener noreferrer") == "google.com" end test "rel attribute iodata" do assert Linkify.link_to_iodata("google.com", rel: "noopener noreferrer") == [ [ "", "google.com", "" ] ] end test "rel as function" do text = "google.com" expected = "google.com" custom_rel = fn url -> url |> String.split(".") |> List.last() end assert Linkify.link(text, rel: custom_rel) == expected text = "google.com" expected = "google.com" custom_rel = fn _ -> nil end assert Linkify.link(text, rel: custom_rel) == expected end test "strip parens" do assert Linkify.link("(google.com)") == "(google.com)" end test "strip parens iodata" do assert Linkify.link_to_iodata("(google.com)") == [["(", ["", "google.com", ""], ")"]] end test "link_map/2" do assert Linkify.link_map("google.com", []) == {"google.com", []} end describe "custom handlers" do test "mentions handler" do text = "hello @user, @valid_user and @invalid_user" valid_users = ["user", "valid_user"] handler = fn "@" <> user = mention, buffer, _opts, acc -> if Enum.member?(valid_users, user) do link = ~s(#{mention}) {link, %{acc | mentions: MapSet.put(acc.mentions, {mention, user})}} else {buffer, acc} end end {result_text, %{mentions: mentions}} = Linkify.link_map(text, %{mentions: MapSet.new()}, mention: true, mention_handler: handler ) assert result_text == "hello @user, @valid_user and @invalid_user" assert mentions |> MapSet.to_list() |> Enum.map(&elem(&1, 1)) == valid_users end test "hashtags handler" do text = "#hello #world" handler = fn hashtag, buffer, opts, acc -> link = Linkify.Builder.create_hashtag_link(hashtag, buffer, opts) {link, %{acc | tags: MapSet.put(acc.tags, hashtag)}} end {result_text, %{tags: tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/user/", rel: false ) assert result_text == "#hello #world" assert MapSet.to_list(tags) == ["#hello", "#world"] text = "#justOne" {result_text, %{tags: _tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/user/", rel: false ) assert result_text == "#justOne" text = "#justOne." {result_text, %{tags: _tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/user/", rel: false ) assert result_text == "#justOne." text = "#justOne " {result_text, %{tags: _tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/user/", rel: false ) assert result_text == "#justOne " text = "#cofe
Source" {_result_text, %{tags: tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/tag/" ) assert MapSet.to_list(tags) == ["#cofe"] text = "#cofe
Source" {_result_text, %{tags: tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/tag/" ) assert MapSet.to_list(tags) == ["#cofe"] text = "#cofeSource" {_result_text, %{tags: tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/tag/" ) assert MapSet.to_list(tags) == ["#cofe"] text = "#cofefetch()" {_result_text, %{tags: tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/tag/" ) assert MapSet.to_list(tags) == ["#cofe"] text = "#cofe
fetch()
" {_result_text, %{tags: tags}} = Linkify.link_map(text, %{tags: MapSet.new()}, hashtag: true, hashtag_handler: handler, hashtag_prefix: "https://example.com/tag/" ) assert MapSet.to_list(tags) == ["#cofe"] end test "mention handler and hashtag prefix" do text = "Hello again, @user.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric" handler = fn "@" <> user = mention, _, _, _ -> ~s(@#{mention}) end expected = ~s(Hello again, @user.<script></script>\nThis is on another :moominmamma: line. #2hu #epic #phantasmagoric) assert Linkify.link(text, mention: true, mention_handler: handler, hashtag: true, hashtag_prefix: "/tag/", new_window: true ) == expected end test "mentions handler with hostname/@user links" do text = "hi @user, take a look at this post: https://example.com/@valid_user/posts/9w5AkQp956XIh74apc" valid_users = ["user", "valid_user"] handler = fn "@" <> user = mention, buffer, _opts, acc -> if Enum.member?(valid_users, user) do link = ~s(#{mention}) {link, %{acc | mentions: MapSet.put(acc.mentions, {mention, user})}} else {buffer, acc} end end {result_text, %{mentions: mentions}} = Linkify.link_map(text, %{mentions: MapSet.new()}, mention: true, mention_handler: handler, new_window: true ) assert result_text == "hi @user, take a look at this post: https://example.com/@valid_user/posts/9w5AkQp956XIh74apc" assert mentions |> MapSet.to_list() |> Enum.map(&elem(&1, 1)) == ["user"] end test "mentions handler and extra links" do text = "hi @user, text me asap xmpp:me@cofe.ai, (or contact me at me@cofe.ai), please.
cofe.ai." valid_users = ["user", "cofe"] handler = fn "@" <> user = mention, buffer, _opts, acc -> if Enum.member?(valid_users, user) do link = ~s(#{mention}) {link, %{acc | mentions: MapSet.put(acc.mentions, {mention, user})}} else {buffer, acc} end end {result_text, %{mentions: mentions}} = Linkify.link_map(text, %{mentions: MapSet.new()}, mention: true, mention_handler: handler, extra: true, email: true ) assert result_text == "hi @user, text me asap xmpp:me@cofe.ai, (or contact me at me@cofe.ai), please.
cofe.ai." assert MapSet.to_list(mentions) == [{"@user", "user"}] end test "mentions handler and emails" do text = "hi @friend, here is my email

user@user.me" valid_users = ["user", "friend"] handler = fn "@" <> user = mention, buffer, _opts, acc -> if Enum.member?(valid_users, user) do link = ~s(#{mention}) {link, %{acc | mentions: MapSet.put(acc.mentions, {mention, user})}} else {buffer, acc} end end {result_text, %{mentions: mentions}} = Linkify.link_map(text, %{mentions: MapSet.new()}, mention: true, mention_handler: handler, extra: true, email: true ) assert result_text == "hi @friend, here is my email

user@user.me" assert MapSet.to_list(mentions) == [{"@friend", "friend"}] end test "href handler" do text = ~s(google.com) result_text = Linkify.link(text, href_handler: &"/redirect?#{URI.encode_query(to: &1)}") assert result_text == ~s(google.com) end end describe "mentions" do test "simple mentions" do expected = ~s{hello @user and @anotherUser.} assert Linkify.link("hello @user and @anotherUser.", mention: true, mention_prefix: "https://example.com/user/", new_window: true ) == expected end test "mentions inside html tags" do text = "

hello world

\n

<`em>another @user__test and @user__test google.com paragraph

\n" expected = "

hello world

\n

<`em>another @user__test and @user__test google.com paragraph

\n" assert Linkify.link(text, mention: true, mention_prefix: "u/") == expected text = "

hi

@user @anotherUser

" expected = "

hi

@user @anotherUser

" assert Linkify.link(text, mention: true, mention_prefix: "u/") == expected end test "mention @user@example.com" do text = "hey @user@example.com" expected = "hey @user@example.com" assert Linkify.link(text, mention: true, mention_prefix: "https://example.com/user/", new_window: true ) == expected expected = "That's @user@example.com's server" text = "That's @user@example.com's server" assert Linkify.link(text, mention: true, mention_prefix: "https://example.com/user/", new_window: true ) == expected end test "mentions with no word-separation before them" do text = "@@example hey! >@@test@example.com idolm@ster" assert Linkify.link(text, mention: true, mention_prefix: "/users/") == text end test "invalid mentions" do text = "hey user@example" assert Linkify.link(text, mention: true, mention_prefix: "/users/") == text end test "IDN domain" do text = "hello @lain@我爱你.com" expected = "hello @lain@我爱你.com" assert Linkify.link(text, mention: true, mention_prefix: "/users/") == expected text = "hello @lain@xn--6qq986b3xl.com" expected = "hello @lain@xn--6qq986b3xl.com" assert Linkify.link(text, mention: true, mention_prefix: "/users/") == expected end test ".onion domain" do text = "Hey @admin@vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion" expected = "Hey @admin@vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion" assert Linkify.link(text, mention: true, mention_prefix: "/users/") == expected end end describe "hashtag links" do test "hashtag" do expected = " one #2two three #four." assert Linkify.link(" one #2two three #four.", hashtag: true, hashtag_prefix: "https://example.com/tag/", new_window: true ) == expected end test "must have non-numbers" do expected = "#1ok #42 #7" assert Linkify.link("#1ok #42 #7", hashtag: true, hashtag_prefix: "/t/", rel: false ) == expected end test "support French" do text = "#administrateur·rice·s #ingénieur·e·s" expected = "#administrateur·rice·s #ingénieur·e·s" assert Linkify.link(text, hashtag: true, hashtag_prefix: "/t/", rel: false ) == expected end test "support Telugu" do text = "#చక్రం #కకకకక్ #కకకకాక #కకకక్రకకకక" expected = "#చక్రం #కకకకక్ #కకకకాక #కకకక్రకకకక" assert Linkify.link(text, hashtag: true, hashtag_prefix: "/t/", rel: false ) == expected end test "do not turn urls with hashes into hashtags" do text = "google.com#test #test google.com/#test #tag" expected = "google.com#test #test google.com/#test #tag" assert Linkify.link(text, hashtag: true, rel: false, hashtag_prefix: "https://example.com/tag/" ) == expected end test "works with non-latin characters" do text = "#漢字 #は #тест #ทดสอบ" expected = "#漢字 #は #тест #ทดสอบ" assert Linkify.link(text, rel: false, hashtag: true, hashtag_prefix: "https://example.com/tag/" ) == expected end test "ZWNJ does not break up hashtags" do text = "#ساٴين‌س" expected = "#ساٴين‌س" assert Linkify.link(text, rel: false, hashtag: true, hashtag_prefix: "https://example.com/tag/" ) == expected end end describe "links" do test "turning urls into links" do text = "Hey, check out http://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." expected = "Hey, check out http://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." assert Linkify.link(text, new_window: true) == expected # no scheme text = "Hey, check out www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." expected = "Hey, check out www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ." assert Linkify.link(text, new_window: true) == expected end test "turn urls with schema into urls" do text = "📌https://google.com" expected = "📌https://google.com" assert Linkify.link(text, rel: false) == expected text = "http://www.cs.vu.nl/~ast/intel/" expected = "http://www.cs.vu.nl/~ast/intel/" assert Linkify.link(text) == expected text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" expected = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087" assert Linkify.link(text) == expected text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" expected = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul" assert Linkify.link(text) == expected text = "https://en.wikipedia.org/wiki/Duff's_device" expected = "https://en.wikipedia.org/wiki/Duff's_device" assert Linkify.link(text) == expected text = "https://1.1.1.1/" expected = "https://1.1.1.1/" assert Linkify.link(text) == expected text = "https://1.1.1.1:8080/" expected = "https://1.1.1.1:8080/" assert Linkify.link(text) == expected end test "strip prefix" do assert Linkify.link("http://google.com", strip_prefix: true) == "google.com" assert Linkify.link("http://www.google.com", strip_prefix: true) == "google.com" end test "hostname/@user" do text = "https://example.com/@user" expected = "https://example.com/@user" assert Linkify.link(text, new_window: true) == expected text = "https://example.com:4000/@user" expected = "https://example.com:4000/@user" assert Linkify.link(text, new_window: true) == expected text = "https://example.com:4000/@user" expected = "https://example.com:4000/@user" assert Linkify.link(text, new_window: true) == expected text = "@username" expected = "@username" assert Linkify.link(text, new_window: true) == expected end end describe "non http links" do test "xmpp" do text = "xmpp:user@example.com" expected = "xmpp:user@example.com" assert Linkify.link(text, extra: true) == expected end test "wrong xmpp" do text = "xmpp:user.example.com" assert Linkify.link(text, extra: true) == text end test "email" do text = "user@example.com" expected = "user@example.com" assert Linkify.link(text, email: true) == expected end test "magnet" do text = "magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce" expected = "magnet:?xt=urn:btih:a4104a9d2f5615601c429fe8bab8177c47c05c84&dn=ubuntu-18.04.1.0-live-server-amd64.iso&tr=http%3A%2F%2Ftorrent.ubuntu.com%3A6969%2Fannounce&tr=http%3A%2F%2Fipv6.torrent.ubuntu.com%3A6969%2Fannounce" assert Linkify.link(text, extra: true) == expected end test "dweb" do text = "dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt" expected = "dweb://584faa05d394190ab1a3f0240607f9bf2b7e2bd9968830a11cf77db0cea36a21+v1.0.0/path/to/file.txt" assert Linkify.link(text, extra: true) == expected end end describe "TLDs" do test "parse with scheme" do text = "https://google.com" expected = "https://google.com" assert Linkify.link(text) == expected end test "only existing TLDs with scheme" do text = "this url https://google.foobar.blah11blah/ has invalid TLD" expected = "this url https://google.foobar.blah11blah/ has invalid TLD" assert Linkify.link(text) == expected text = "this url https://google.foobar.com/ has valid TLD" expected = "this url https://google.foobar.com/ has valid TLD" assert Linkify.link(text) == expected end test "only existing TLDs without scheme" do text = "this url google.foobar.blah11blah/ has invalid TLD" assert Linkify.link(text) == text text = "this url google.foobar.com/ has valid TLD" expected = "this url google.foobar.com/ has valid TLD" assert Linkify.link(text) == expected end test "only existing TLDs with and without scheme" do text = "this url http://google.foobar.com/ has valid TLD" expected = "this url http://google.foobar.com/ has valid TLD" assert Linkify.link(text) == expected text = "this url google.foobar.com/ has valid TLD" expected = "this url google.foobar.com/ has valid TLD" assert Linkify.link(text) == expected end test "FQDN (with trailing period)" do text = "Check out this article: https://www.wired.com./story/marissa-mayer-startup-sunshine-contacts/" expected = "Check out this article: https://www.wired.com./story/marissa-mayer-startup-sunshine-contacts/" assert Linkify.link(text) == expected end test "Do not link trailing punctuation" do text = "You can find more info at https://pleroma.social." expected = "You can find more info at https://pleroma.social." assert Linkify.link(text) == expected text = "Of course it was google.com!!" expected = "Of course it was google.com!!" assert Linkify.link(text) == expected text = "First I had to login to hotmail.com, then I had to delete emails because my 15MB quota was full." expected = "First I had to login to hotmail.com, then I had to delete emails because my 15MB quota was full." assert Linkify.link(text) == expected text = "I looked at theonion.com; it was no longer funny." expected = "I looked at theonion.com; it was no longer funny." assert Linkify.link(text) == expected end test "IDN and punycode domain" do text = "FrauBücher.com says Neiiighhh!" expected = "FrauBücher.com says Neiiighhh!" assert Linkify.link(text) == expected text = "xn--fraubcher-u9a.com says Neiiighhh!" expected = "xn--fraubcher-u9a.com says Neiiighhh!" assert Linkify.link(text) == expected end test ".onion domain" do text = "The riseup.net hidden service is at vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion" expected = "The riseup.net hidden service is at vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion" assert Linkify.link(text) == expected end test "IPv4 is linked only with scheme" do text = "1.1.1.1" assert Linkify.link(text) == text text = "http://1.1.1.1" expected = "http://1.1.1.1" assert Linkify.link(text) == expected end test "shortened IPv4 are not linked" do text = "109.99" expected = "109.99" assert Linkify.link(text) == expected end test "URLs with last character is closing paren" do text = "Have you read https://en.wikipedia.org/wiki/Frame_(networking)?" expected = "Have you read https://en.wikipedia.org/wiki/Frame_(networking)?" assert Linkify.link(text) == expected end test "works with URLs ending in unbalanced closed paren, no path separator, and no query params" do text = "http://example.com)" expected = "http://example.com)" assert Linkify.link(text) == expected end end end