diff --git a/html.go b/html.go index 31f786b..ba10825 100644 --- a/html.go +++ b/html.go @@ -485,7 +485,7 @@ func (options *Html) Emphasis(out *bytes.Buffer, text []byte) { } func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) { - if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) { + if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { out.WriteString(options.parameters.AbsolutePrefix) if link[0] != '/' { out.WriteByte('/') @@ -890,6 +890,17 @@ func isRelativeLink(link []byte) (yes bool) { if len(link) == 1 && link[0] == '/' { yes = true } + + // current directory : begin with "./" + if len(link) >= 2 && link[0] == '.' && link[1] == '/' { + yes = true + } + + // parent directory : begin with "../" + if len(link) >= 3 && link[0] == '.' && link[1] == '.' && link[2] == '/' { + yes = true + } + return } diff --git a/inline.go b/inline.go index fe45aa0..2e30e95 100644 --- a/inline.go +++ b/inline.go @@ -757,9 +757,20 @@ func isEndOfLink(char byte) bool { return isspace(char) || char == '<' } -var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://"), []byte("/")} +var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")} +var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")} func isSafeLink(link []byte) bool { + for _, path := range validPaths { + if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) { + if len(link) == len(path) { + return true + } else if isalnum(link[len(path)]) { + return true + } + } + } + for _, prefix := range validUris { // TODO: handle unicode here // case-insensitive prefix test diff --git a/inline_test.go b/inline_test.go index bf362e0..e540b8a 100644 --- a/inline_test.go +++ b/inline_test.go @@ -72,6 +72,7 @@ func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int, input := tests[i] candidate = input expected := tests[i+1] + actual := runMarkdownInline(candidate, extensions, htmlFlags, params) if actual != expected { t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]", @@ -440,6 +441,15 @@ func TestInlineLink(t *testing.T) { "[[t]](/t)\n", "

[t]

\n", + + "[link]()\n", + "

link

\n", + + "[link](<./>)\n", + "

link

\n", + + "[link](<../>)\n", + "

link

\n", } doLinkTestsInline(t, tests) @@ -452,6 +462,18 @@ func TestRelAttrLink(t *testing.T) { "[foo](/bar/)\n", "

foo

\n", + + "[foo](/)\n", + "

foo

\n", + + "[foo](./)\n", + "

foo

\n", + + "[foo](../)\n", + "

foo

\n", + + "[foo](../bar)\n", + "

foo

\n", } doTestsInlineParam(t, nofollowTests, 0, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, HtmlRendererParameters{}) @@ -483,6 +505,21 @@ func TestHrefTargetBlank(t *testing.T) { "[foo](/bar/)\n", "

foo

\n", + "[foo](/)\n", + "

foo

\n", + + "[foo](./)\n", + "

foo

\n", + + "[foo](./bar)\n", + "

foo

\n", + + "[foo](../)\n", + "

foo

\n", + + "[foo](../bar)\n", + "

foo

\n", + "[foo](http://example.com)\n", "

foo

\n", } @@ -494,6 +531,15 @@ func TestSafeInlineLink(t *testing.T) { "[foo](/bar/)\n", "

foo

\n", + "[foo](/)\n", + "

foo

\n", + + "[foo](./)\n", + "

foo

\n", + + "[foo](../)\n", + "

foo

\n", + "[foo](http://bar/)\n", "

foo

\n", @@ -541,6 +587,9 @@ func TestReferenceLink(t *testing.T) { "[ref]\n [ref]: /url/ \"title\"\n", "

ref

\n", + + "[ref]\n [ref]: ../url/ \"title\"\n", + "

ref

\n", } doLinkTestsInline(t, tests) }