From 8335b556d1049d364080cdc3684e1df499b6ab04 Mon Sep 17 00:00:00 2001 From: Ethan Koenig Date: Thu, 17 Aug 2017 00:03:41 -0700 Subject: [PATCH] Fix rendering of external links (#2292) (#2315) --- modules/markdown/markdown.go | 54 +++++++++---------- modules/markdown/markdown_test.go | 78 ++++++++++------------------ routers/api/v1/misc/markdown_test.go | 2 +- routers/init.go | 1 + 4 files changed, 56 insertions(+), 79 deletions(-) diff --git a/modules/markdown/markdown.go b/modules/markdown/markdown.go index bccc777f0..be3058cfc 100644 --- a/modules/markdown/markdown.go +++ b/modules/markdown/markdown.go @@ -79,12 +79,29 @@ var ( // AnySHA1Pattern allows to split url containing SHA into parts AnySHA1Pattern = regexp.MustCompile(`(http\S*)://(\S+)/(\S+)/(\S+)/(\S+)/([0-9a-f]{40})(?:/?([^#\s]+)?(?:#(\S+))?)?`) - // IssueFullPattern allows to split issue (and pull) URLs into parts - IssueFullPattern = regexp.MustCompile(`(?:^|\s|\()(http\S*)://((?:[^\s/]+/)+)((?:\w{1,10}-)?[1-9][0-9]*)([\?|#]\S+.(\S+)?)?\b`) - validLinksPattern = regexp.MustCompile(`^[a-z][\w-]+://`) ) +// regexp for full links to issues/pulls +var issueFullPattern *regexp.Regexp + +// InitMarkdown initialize regexps for markdown parsing +func InitMarkdown() { + getIssueFullPattern() +} + +func getIssueFullPattern() *regexp.Regexp { + if issueFullPattern == nil { + appURL := setting.AppURL + if len(appURL) > 0 && appURL[len(appURL)-1] != '/' { + appURL += "/" + } + issueFullPattern = regexp.MustCompile(appURL + + `\w+/\w+/(?:issues|pulls)/((?:\w{1,10}-)?[1-9][0-9]*)([\?|#]\S+.(\S+)?)?\b`) + } + return issueFullPattern +} + // isLink reports whether link fits valid format. func isLink(link []byte) bool { return validLinksPattern.Match(link) @@ -352,32 +369,17 @@ func renderFullSha1Pattern(rawBytes []byte, urlPrefix string) []byte { return rawBytes } -// renderFullIssuePattern renders issues-like URLs -func renderFullIssuePattern(rawBytes []byte, urlPrefix string) []byte { - ms := IssueFullPattern.FindAllSubmatch(rawBytes, -1) +// RenderFullIssuePattern renders issues-like URLs +func RenderFullIssuePattern(rawBytes []byte) []byte { + ms := getIssueFullPattern().FindAllSubmatch(rawBytes, -1) for _, m := range ms { all := m[0] - protocol := string(m[1]) - paths := bytes.Split(m[2], []byte("/")) - paths = paths[:len(paths)-1] - if bytes.HasPrefix(paths[0], []byte("gist.")) { - continue - } - path := protocol + "://" + string(m[2]) - id := string(m[3]) - path = URLJoin(path, id) - var comment []byte - if len(m) > 3 { - comment = m[4] - } - urlSuffix := "" + id := string(m[1]) text := "#" + id - if comment != nil { - urlSuffix += string(comment) - text += " " - } + // TODO if m[2] is not nil, then link is to a comment, + // and we should indicate that in the text somehow rawBytes = bytes.Replace(rawBytes, all, []byte(fmt.Sprintf( - `%s`, path, urlSuffix, text)), -1) + `%s`, string(all), text)), -1) } return rawBytes } @@ -579,12 +581,12 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string, metas map[string]strin []byte(fmt.Sprintf(`%s`, URLJoin(setting.AppURL, string(m[1:])), m)), -1) } + rawBytes = RenderFullIssuePattern(rawBytes) rawBytes = RenderShortLinks(rawBytes, urlPrefix, false, isWikiMarkdown) rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix, metas) rawBytes = RenderCrossReferenceIssueIndexPattern(rawBytes, urlPrefix, metas) rawBytes = renderFullSha1Pattern(rawBytes, urlPrefix) rawBytes = renderSha1CurrentPattern(rawBytes, urlPrefix) - rawBytes = renderFullIssuePattern(rawBytes, urlPrefix) return rawBytes } diff --git a/modules/markdown/markdown_test.go b/modules/markdown/markdown_test.go index 1f25ec9bd..72cb7bd5e 100644 --- a/modules/markdown/markdown_test.go +++ b/modules/markdown/markdown_test.go @@ -180,13 +180,15 @@ func TestRender_AutoLink(t *testing.T) { numericIssueLink(URLJoin(setting.AppSubURL, "issues"), 3333)) // render external issue URLs - tmp := "http://1111/2222/ssss-issues/3333?param=blah&blahh=333" - test(tmp, "#3333 ") - test("http://test.com/issues/33333", numericIssueLink("http://test.com/issues", 33333)) - test("https://issues/333", numericIssueLink("https://issues", 333)) + for _, externalURL := range []string{ + "http://1111/2222/ssss-issues/3333?param=blah&blahh=333", + "http://test.com/issues/33333", + "https://issues/333"} { + test(externalURL, externalURL) + } // render valid commit URLs - tmp = URLJoin(AppSubURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") + tmp := URLJoin(AppSubURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") test(tmp, "d8a994ef24") tmp += "#diff-2" test(tmp, "d8a994ef24 (diff-2)") @@ -332,6 +334,22 @@ func TestRender_CrossReferences(t *testing.T) { `

gogits/gogs#12345

`) } +func TestRender_FullIssueURLs(t *testing.T) { + setting.AppURL = AppURL + setting.AppSubURL = AppSubURL + + test := func(input, expected string) { + result := RenderFullIssuePattern([]byte(input)) + assert.Equal(t, expected, string(result)) + } + test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6", + "Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6") + test("Look here http://localhost:3000/person/repo/issues/4", + `Look here #4`) + test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", + `#4`) +} + func TestRegExp_MentionPattern(t *testing.T) { trueTestCases := []string{ "@Unknwon", @@ -522,50 +540,6 @@ func TestRegExp_AnySHA1Pattern(t *testing.T) { } } -func TestRegExp_IssueFullPattern(t *testing.T) { - testCases := map[string][]string{ - "https://github.com/gogits/gogs/pull/3244": { - "https", - "github.com/gogits/gogs/pull/", - "3244", - "", - "", - }, - "https://github.com/gogits/gogs/issues/3247#issuecomment-231517079": { - "https", - "github.com/gogits/gogs/issues/", - "3247", - "#issuecomment-231517079", - "", - }, - "https://try.gogs.io/gogs/gogs/issues/4#issue-685": { - "https", - "try.gogs.io/gogs/gogs/issues/", - "4", - "#issue-685", - "", - }, - "https://youtrack.jetbrains.com/issue/JT-36485": { - "https", - "youtrack.jetbrains.com/issue/", - "JT-36485", - "", - "", - }, - "https://youtrack.jetbrains.com/issue/JT-36485#comment=27-1508676": { - "https", - "youtrack.jetbrains.com/issue/", - "JT-36485", - "#comment=27-1508676", - "", - }, - } - - for k, v := range testCases { - assert.Equal(t, IssueFullPattern.FindStringSubmatch(k)[1:], v) - } -} - func TestMisc_IsMarkdownFile(t *testing.T) { setting.Markdown.FileExtensions = []string{".md", ".markdown", ".mdown", ".mkd"} trueTestCases := []string{ @@ -634,7 +608,7 @@ var sameCases = []string{ Ideas and codes -- Bezier widget (by @r-lyeh) https://github.com/ocornut/imgui/issues/786 +- Bezier widget (by @r-lyeh) ` + AppURL + `ocornut/imgui/issues/786 - Node graph editors https://github.com/ocornut/imgui/issues/306 - [[Memory Editor|memory_editor_example]] - [[Plot var helper|plot_var_example]]`, @@ -670,8 +644,8 @@ func testAnswers(baseURLContent, baseURLImages string) []string {

Ideas and codes

diff --git a/routers/api/v1/misc/markdown_test.go b/routers/api/v1/misc/markdown_test.go index 182b14782..d6e619347 100644 --- a/routers/api/v1/misc/markdown_test.go +++ b/routers/api/v1/misc/markdown_test.go @@ -75,7 +75,7 @@ func TestAPI_RenderGFM(t *testing.T) { `, // wine-staging wiki home extract: special wiki syntax, images diff --git a/routers/init.go b/routers/init.go index dec7f1818..6d1b2dd63 100644 --- a/routers/init.go +++ b/routers/init.go @@ -49,6 +49,7 @@ func GlobalInit() { if setting.InstallLock { highlight.NewContext() + markdown.InitMarkdown() markdown.NewSanitizer() if err := models.NewEngine(); err != nil { log.Fatal(4, "Failed to initialize ORM engine: %v", err)