From d436de90b1f6398a9174997c95046c0307a1f5fb Mon Sep 17 00:00:00 2001 From: Beowulf Date: Sun, 21 Dec 2025 05:21:27 +0100 Subject: [PATCH] Add to html button in markdown `type="button"` (#10520) This is for preventing that a markdown button is recognized as button for submission in a html form. Buttons can't be stripped from the markdown due to: https://codeberg.org/forgejo/forgejo/pulls/7670#issuecomment-4086608 There is no issue with buttons if they always have `type="button"`, so this should be fine. This is a "follow-up" to !7670. Fixes #7656 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10520 Reviewed-by: Otto Reviewed-by: Gusted Co-authored-by: Beowulf Co-committed-by: Beowulf --- modules/markup/markdown/goldmark.go | 3 ++ modules/markup/markdown/markdown_test.go | 27 +++++++++++++++ modules/markup/markdown/transform_html.go | 28 +++++++++++++++ modules/markup/sanitizer.go | 6 +++- .../issue-comment-file-preview.test.e2e.ts | 34 +++++++++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 modules/markup/markdown/transform_html.go create mode 100644 tests/e2e/issue-comment-file-preview.test.e2e.ts diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 8a3da3b08f..1ea3375ab5 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -1,4 +1,5 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package markdown @@ -87,6 +88,8 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa if scope, found := ctx.Metas["scope"]; found { v.Name = fmt.Appendf(v.Name, "-%s", scope) } + case *ast.RawHTML: + g.transformRawHTML(ctx, v, reader) } return ast.WalkContinue, nil }) diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 7c13494a67..82c2c7fe8c 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -1,4 +1,5 @@ // Copyright 2017 The Gitea Authors. All rights reserved. +// Copyright 2025 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package markdown_test @@ -125,6 +126,32 @@ func TestRender_Images(t *testing.T) { `

`+title+`

`) } +func TestRender_Buttons(t *testing.T) { + setting.AppURL = AppURL + + test := func(input, expected string) { + buffer, err := markdown.RenderString(&markup.RenderContext{ + Ctx: git.DefaultContext, + Links: markup.Links{ + Base: FullURL, + }, + }, input) + require.NoError(t, err) + assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer))) + } + + test( + "", + `

`) + + test( + ``, + `

`) + test( + ``, + `

`) +} + func testAnswers(baseURLContent, baseURLImages string) []string { return []string{ `

Wiki! Enjoy :)

diff --git a/modules/markup/markdown/transform_html.go b/modules/markup/markdown/transform_html.go new file mode 100644 index 0000000000..9bebb45554 --- /dev/null +++ b/modules/markup/markdown/transform_html.go @@ -0,0 +1,28 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package markdown + +import ( + "strings" + + "forgejo.org/modules/markup" + + "github.com/yuin/goldmark/ast" + "github.com/yuin/goldmark/text" +) + +func (g *ASTTransformer) addTypeToButton(v *ast.RawHTML, segment string) { + segment = strings.TrimPrefix(segment, " 0 { policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...) diff --git a/tests/e2e/issue-comment-file-preview.test.e2e.ts b/tests/e2e/issue-comment-file-preview.test.e2e.ts new file mode 100644 index 0000000000..03f7557dc0 --- /dev/null +++ b/tests/e2e/issue-comment-file-preview.test.e2e.ts @@ -0,0 +1,34 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +// @watch start +// modules/markup/** +// web_src/js/features/repo-unicode-escape.js +// @watch end + +import {expect} from '@playwright/test'; +import {dynamic_id, test} from './utils_e2e.ts'; + +test.use({user: 'user2'}); + +test('Escape button in file preview', async ({page}) => { + await page.goto('/user2/unicode-escaping/src/branch/main/a-file'); + + const url = await page.getByRole('link', {name: 'Permalink'}).getAttribute('href'); + + const response = await page.goto('/user2/repo1/issues/new'); + expect(response?.status()).toBe(200); + + // Create a new issue + await page.getByPlaceholder('Title').fill(dynamic_id()); + await page.getByPlaceholder('Leave a comment').fill(`http://localhost:3003${url}#L1`); + await page.getByRole('button', {name: 'Create issue'}).click(); + + await expect(page).toHaveURL(/\/user2\/repo1\/issues\/\d+$/); + + await expect(page.locator('table.file-preview.unicode-escaped')).toHaveCount(0); + await expect(async () => { + await page.locator('button.toggle-escape-button').click(); + await expect(page.locator('table.file-preview.unicode-escaped')).toHaveCount(1); + }).toPass(); +});