Skip to content

Commit

Permalink
Refactor markup render system (#32533)
Browse files Browse the repository at this point in the history
Remove unmaintainable sanitizer rules. No need to add special "class"
regexp rules anymore, use RenderInternal.SafeAttr instead, more details
(and examples) are in the tests
  • Loading branch information
wxiaoguang authored Nov 18, 2024
1 parent 4f879a0 commit 8a20fba
Show file tree
Hide file tree
Showing 42 changed files with 567 additions and 507 deletions.
25 changes: 0 additions & 25 deletions modules/html/html.go

This file was deleted.

48 changes: 48 additions & 0 deletions modules/htmlutil/html.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package htmlutil

import (
"fmt"
"html/template"
"slices"
)

// ParseSizeAndClass get size and class from string with default values
// If present, "others" expects the new size first and then the classes to use
func ParseSizeAndClass(defaultSize int, defaultClass string, others ...any) (int, string) {
size := defaultSize
if len(others) >= 1 {
if v, ok := others[0].(int); ok && v != 0 {
size = v
}
}
class := defaultClass
if len(others) >= 2 {
if v, ok := others[1].(string); ok && v != "" {
if class != "" {
class += " "
}
class += v
}
}
return size, class
}

func HTMLFormat(s string, rawArgs ...any) template.HTML {
args := slices.Clone(rawArgs)
for i, v := range args {
switch v := v.(type) {
case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, template.HTML:
// for most basic types (including template.HTML which is safe), just do nothing and use it
case string:
args[i] = template.HTMLEscapeString(v)
case fmt.Stringer:
args[i] = template.HTMLEscapeString(v.String())
default:
args[i] = template.HTMLEscapeString(fmt.Sprint(v))
}
}
return template.HTML(fmt.Sprintf(s, args...))
}
15 changes: 15 additions & 0 deletions modules/htmlutil/html_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package htmlutil

import (
"html/template"
"testing"

"github.com/stretchr/testify/assert"
)

func TestHTMLFormat(t *testing.T) {
assert.Equal(t, template.HTML("<a>&lt; < 1</a>"), HTMLFormat("<a>%s %s %d</a>", "<", template.HTML("<"), 1))
}
15 changes: 2 additions & 13 deletions modules/markup/asciicast/asciicast.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"fmt"
"io"
"net/url"
"regexp"

"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -38,10 +37,7 @@ const (

// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{
{Element: "div", AllowAttr: "class", Regexp: regexp.MustCompile(playerClassName)},
{Element: "div", AllowAttr: playerSrcAttr},
}
return []setting.MarkupSanitizerRule{{Element: "div", AllowAttr: playerSrcAttr}}
}

// Render implements markup.Renderer
Expand All @@ -53,12 +49,5 @@ func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer)
ctx.Metas["BranchNameSubURL"],
url.PathEscape(ctx.RelativePath),
)

_, err := io.WriteString(output, fmt.Sprintf(
`<div class="%s" %s="%s"></div>`,
playerClassName,
playerSrcAttr,
rawURL,
))
return err
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)
}
16 changes: 0 additions & 16 deletions modules/markup/common/html.go

This file was deleted.

18 changes: 15 additions & 3 deletions modules/markup/common/linkify.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,27 @@ package common
import (
"bytes"
"regexp"
"sync"

"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
"mvdan.cc/xurls/v2"
)

var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
type GlobalVarsType struct {
wwwURLRegxp *regexp.Regexp
LinkRegex *regexp.Regexp // fast matching a URL link, no any extra validation.
}

var GlobalVars = sync.OnceValue[*GlobalVarsType](func() *GlobalVarsType {
v := &GlobalVarsType{}
v.wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}((?:/|[#?])[-a-zA-Z0-9@:%_\+.~#!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
v.LinkRegex, _ = xurls.StrictMatchingScheme("https?://")
return v
})

type linkifyParser struct{}

Expand Down Expand Up @@ -60,10 +72,10 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont
var protocol []byte
typ := ast.AutoLinkURL
if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) {
m = LinkRegex.FindSubmatchIndex(line)
m = GlobalVars().LinkRegex.FindSubmatchIndex(line)
}
if m == nil && bytes.HasPrefix(line, domainWWW) {
m = wwwURLRegxp.FindSubmatchIndex(line)
m = GlobalVars().wwwURLRegxp.FindSubmatchIndex(line)
protocol = []byte("http")
}
if m != nil {
Expand Down
7 changes: 3 additions & 4 deletions modules/markup/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ package console
import (
"bytes"
"io"
"path/filepath"
"regexp"
"path"

"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -36,7 +35,7 @@ func (Renderer) Extensions() []string {
// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{
{Element: "span", AllowAttr: "class", Regexp: regexp.MustCompile(`^term-((fg[ix]?|bg)\d+|container)$`)},
{Element: "span", AllowAttr: "class", Regexp: `^term-((fg[ix]?|bg)\d+|container)$`},
}
}

Expand All @@ -46,7 +45,7 @@ func (Renderer) CanRender(filename string, input io.Reader) bool {
if err != nil {
return false
}
if enry.GetLanguage(filepath.Base(filename), buf) != enry.OtherLanguage {
if enry.GetLanguage(path.Base(filename), buf) != enry.OtherLanguage {
return false
}
return bytes.ContainsRune(buf, '\x1b')
Expand Down
11 changes: 5 additions & 6 deletions modules/markup/csv/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"bufio"
"html"
"io"
"regexp"
"strconv"

"code.gitea.io/gitea/modules/csv"
Expand Down Expand Up @@ -37,9 +36,9 @@ func (Renderer) Extensions() []string {
// SanitizerRules implements markup.Renderer
func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return []setting.MarkupSanitizerRule{
{Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`data-table`)},
{Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
{Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)},
{Element: "table", AllowAttr: "class", Regexp: `^data-table$`},
{Element: "th", AllowAttr: "class", Regexp: `^line-num$`},
{Element: "td", AllowAttr: "class", Regexp: `^line-num$`},
}
}

Expand All @@ -51,13 +50,13 @@ func writeField(w io.Writer, element, class, field string) error {
return err
}
if len(class) > 0 {
if _, err := io.WriteString(w, " class=\""); err != nil {
if _, err := io.WriteString(w, ` class="`); err != nil {
return err
}
if _, err := io.WriteString(w, class); err != nil {
return err
}
if _, err := io.WriteString(w, "\""); err != nil {
if _, err := io.WriteString(w, `"`); err != nil {
return err
}
}
Expand Down
9 changes: 4 additions & 5 deletions modules/markup/external/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.

_, err = io.Copy(f, input)
if err != nil {
f.Close()
_ = f.Close()
return fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err)
}

Expand All @@ -113,10 +113,9 @@ func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.
args = append(args, f.Name())
}

if ctx == nil || ctx.Ctx == nil {
if ctx == nil {
log.Warn("RenderContext not provided defaulting to empty ctx")
ctx = &markup.RenderContext{}
if ctx.Ctx == nil {
if !setting.IsProd || setting.IsInTesting {
panic("RenderContext did not provide context")
}
log.Warn("RenderContext did not provide context, defaulting to Shutdown context")
ctx.Ctx = graceful.GetManager().ShutdownContext()
Expand Down
Loading

0 comments on commit 8a20fba

Please sign in to comment.