// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package notifications import ( "bytes" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yuin/goldmark" ) func TestEscapeMarkdown(t *testing.T) { tests := []struct { name string in string want string }{ {"empty", "", ""}, {"plain ASCII", "Hello World", "Hello World"}, {"backslash", `a\b`, `a\\b`}, {"link open bracket", "a[b", `a\[b`}, {"link close bracket", "a]b", `a\]b`}, {"paren open", "a(b", `a\(b`}, {"paren close", "a)b", `a\)b`}, {"image bang", "a!b", `a\!b`}, {"emphasis asterisk", "a*b", `a\*b`}, {"emphasis underscore", "a_b", `a\_b`}, {"code backtick", "a`b", "a\\`b"}, {"heading hash", "a#b", `a\#b`}, {"blockquote", "a>b", `a\>b`}, {"list dash", "a-b", `a\-b`}, {"list plus", "a+b", `a\+b`}, {"list dot", "a.b", `a\.b`}, {"pipe (tables)", "a|b", `a\|b`}, {"tilde (strikethrough)", "a~b", `a\~b`}, {"curly brace open", "a{b", `a\{b`}, {"curly brace close", "a}b", `a\}b`}, {"angle bracket open", "a", `\`}, {"raw html tag", `x`, `\x\`}, {"raw img tag", ``, `\`}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := EscapeMarkdown(tt.in) assert.Equal(t, tt.want, got) }) } } // TestEscapeMarkdown_RoundTripThroughGoldmark verifies that every escaped // string, when fed into goldmark as the text portion of a Markdown link, // renders as the original literal text and does NOT produce any additional // or elements from injected Markdown syntax. func TestEscapeMarkdown_RoundTripThroughGoldmark(t *testing.T) { payloads := []string{ "test](https://evil.com) [Click to verify", "![](https://evil.com/track.png)", "plain title", `a\b`, "`code`", "*bold*", "", `click`, ``, } for _, p := range payloads { t.Run(p, func(t *testing.T) { // Embed in a markdown link and a free paragraph. md := "* [" + EscapeMarkdown(p) + "](https://vikunja.io/safe)\n\n" + EscapeMarkdown(p) var buf bytes.Buffer require.NoError(t, goldmark.Convert([]byte(md), &buf)) html := buf.String() // There must be exactly one tag: %s", p, html) // The safe URL must still be present. assert.Contains(t, html, `href="https://vikunja.io/safe"`) }) } }