Преглед на файлове

markup: expand test coverage and sanitize notice banner output

Add previously covered autolink test cases that were dropped during the
goldmark migration (hosts without dots, https variants, single-digit
issues, cross-repo issues). Add new test suites for link rewriting with
both path-only and absolute URL prefixes, and for HTML passthrough
behavior confirming raw HTML is stripped without WithUnsafe.

Sanitize RawMarkdown output in the server notice banner to prevent
potential XSS, since it was the only call site not passing through
SanitizeBytes.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
JSS преди 4 дни
родител
ревизия
997011bfb2
променени са 2 файла, в които са добавени 101 реда и са изтрити 1 реда
  1. 1 1
      internal/context/notice.go
  2. 100 0
      internal/markup/markdown_test.go

+ 1 - 1
internal/context/notice.go

@@ -54,5 +54,5 @@ func (c *Context) renderNoticeBanner() {
 		return
 	}
 
-	c.Data["ServerNotice"] = string(markup.RawMarkdown(buf, ""))
+	c.Data["ServerNotice"] = string(markup.SanitizeBytes(markup.RawMarkdown(buf, "")))
 }

+ 100 - 0
internal/markup/markdown_test.go

@@ -73,6 +73,106 @@ func Test_RawMarkdown_AutoLink(t *testing.T) {
 			input: "https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2",
 			want:  "<p><a href=\"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2</a></p>\n",
 		},
+		{
+			name:  "issue URL with single digit",
+			input: "http://test.com/issues/3",
+			want:  "<p><a href=\"http://test.com/issues/3\">http://test.com/issues/3</a></p>\n",
+		},
+		{
+			name:  "host without dot in issue-like URL",
+			input: "http://issues/333",
+			want:  "<p><a href=\"http://issues/333\">http://issues/333</a></p>\n",
+		},
+		{
+			name:  "https host without dot in issue-like URL",
+			input: "https://issues/333",
+			want:  "<p><a href=\"https://issues/333\">https://issues/333</a></p>\n",
+		},
+		{
+			name:  "host without dot resembling keyword",
+			input: "http://tissues/0",
+			want:  "<p><a href=\"http://tissues/0\">http://tissues/0</a></p>\n",
+		},
+		{
+			name:  "https commit-like URL without dot",
+			input: "https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae",
+			want:  "<p><a href=\"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae</a></p>\n",
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			got := string(RawMarkdown([]byte(test.input), ""))
+			assert.Equal(t, test.want, got)
+		})
+	}
+
+	t.Run("cross-repo issue URL from same instance", func(t *testing.T) {
+		got := string(RawMarkdown([]byte("http://localhost:3000/other/repo/issues/42"), "/user/myrepo"))
+		assert.Equal(t, "<p><a href=\"http://localhost:3000/other/repo/issues/42\">other/repo#42</a></p>\n", got)
+	})
+
+	t.Run("same-repo issue URL with fragment", func(t *testing.T) {
+		got := string(RawMarkdown([]byte("http://localhost:3000/user/myrepo/issues/42#issuecomment-1"), "/user/myrepo"))
+		assert.Equal(t, "<p><a href=\"http://localhost:3000/user/myrepo/issues/42#issuecomment-1\">#42</a></p>\n", got)
+	})
+}
+
+func Test_RawMarkdown_LinkRewriting(t *testing.T) {
+	tests := []struct {
+		name      string
+		input     string
+		urlPrefix string
+		want      string
+	}{
+		{
+			name:      "relative link with path-only prefix",
+			input:     "[text](other-file.md)",
+			urlPrefix: "/user/repo/src/branch/main",
+			want:      "<p><a href=\"/user/repo/src/branch/main/other-file.md\">text</a></p>\n",
+		},
+		{
+			name:      "relative link with absolute URL prefix",
+			input:     "[text](other-file.md)",
+			urlPrefix: "http://localhost:3000/user/repo/src/branch/main",
+			want:      "<p><a href=\"http://localhost:3000/user/repo/src/branch/main/other-file.md\">text</a></p>\n",
+		},
+		{
+			name:      "absolute link not rewritten",
+			input:     "[text](https://example.com/page)",
+			urlPrefix: "/user/repo/src/branch/main",
+			want:      "<p><a href=\"https://example.com/page\">text</a></p>\n",
+		},
+		{
+			name:      "anchor-only link not rewritten",
+			input:     "[text](#section)",
+			urlPrefix: "/user/repo/src/branch/main",
+			want:      "<p><a href=\"#section\">text</a></p>\n",
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			got := string(RawMarkdown([]byte(test.input), test.urlPrefix))
+			assert.Equal(t, test.want, got)
+		})
+	}
+}
+
+func Test_RawMarkdown_HTMLPassthrough(t *testing.T) {
+	tests := []struct {
+		name  string
+		input string
+		want  string
+	}{
+		{
+			name:  "inline HTML tags are stripped",
+			input: "Hello <em>world</em>",
+			want:  "<p>Hello <!-- raw HTML omitted -->world<!-- raw HTML omitted --></p>\n",
+		},
+		{
+			name:  "block HTML tags are stripped",
+			input: "<div>content</div>",
+			want:  "<!-- raw HTML omitted -->\n",
+		},
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {