From 76c5d8a9b2b7f1f369b7769d04c028acb5b4be3f Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 17 Mar 2026 15:20:06 +0000 Subject: [PATCH] appview/pages: clone rctx before transforming markdown avoid global mutation and weird cross-request interactions. Signed-off-by: oppiliappan --- appview/pages/funcmap.go | 21 ++++++++++++--------- appview/pages/markup/markdown.go | 9 +++++++++ appview/pages/pages.go | 41 ++++++++++++++++++++--------------------- 3 file(s) changed, 41 insertion(s)(+), 30 deletion(s)(-) diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -265,25 +265,28 @@ } return v.Slice(0, min(n, v.Len())).Interface() }, "markdown": func(text string) template.HTML { - p.rctx.RendererType = markup.RendererTypeDefault - htmlString := p.rctx.RenderMarkdown(text) - sanitized := p.rctx.SanitizeDefault(htmlString) + rctx := p.rctx.Clone() + rctx.RendererType = markup.RendererTypeDefault + htmlString := rctx.RenderMarkdown(text) + sanitized := rctx.SanitizeDefault(htmlString) return template.HTML(sanitized) }, "description": func(text string) template.HTML { - p.rctx.RendererType = markup.RendererTypeDefault - htmlString := p.rctx.RenderMarkdownWith(text, goldmark.New( + rctx := p.rctx.Clone() + rctx.RendererType = markup.RendererTypeDefault + htmlString := rctx.RenderMarkdownWith(text, goldmark.New( goldmark.WithExtensions( emoji.Emoji, ), )) - sanitized := p.rctx.SanitizeDescription(htmlString) + sanitized := rctx.SanitizeDescription(htmlString) return template.HTML(sanitized) }, "readme": func(text string) template.HTML { - p.rctx.RendererType = markup.RendererTypeRepoMarkdown - htmlString := p.rctx.RenderMarkdown(text) - sanitized := p.rctx.SanitizeDefault(htmlString) + rctx := p.rctx.Clone() + rctx.RendererType = markup.RendererTypeRepoMarkdown + htmlString := rctx.RenderMarkdown(text) + sanitized := rctx.SanitizeDefault(htmlString) return template.HTML(sanitized) }, "code": func(content, path string) string { diff --git a/appview/pages/markup/markdown.go b/appview/pages/markup/markdown.go --- a/appview/pages/markup/markdown.go +++ b/appview/pages/markup/markdown.go @@ -86,6 +86,15 @@ ) return md } +// clone creates a shallow copy of the RenderContext +func (rctx *RenderContext) Clone() *RenderContext { + if rctx == nil { + return nil + } + clone := *rctx + return &clone +} + // NewMarkdownWith is an alias for NewMarkdown with extra extensions. func NewMarkdownWith(hostname string, extra ...goldmark.Extender) goldmark.Markdown { return NewMarkdown(hostname, extra...) diff --git a/appview/pages/pages.go b/appview/pages/pages.go --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -350,9 +350,10 @@ if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) } - p.rctx.RendererType = markup.RendererTypeDefault - htmlString := p.rctx.RenderMarkdown(string(markdownBytes)) - sanitized := p.rctx.SanitizeDefault(htmlString) + rctx := p.rctx.Clone() + rctx.RendererType = markup.RendererTypeDefault + htmlString := rctx.RenderMarkdown(string(markdownBytes)) + sanitized := rctx.SanitizeDefault(htmlString) params.Content = template.HTML(sanitized) return p.execute("legal/terms", w, params) @@ -378,9 +379,10 @@ if err != nil { return fmt.Errorf("failed to read %s: %w", filename, err) } - p.rctx.RendererType = markup.RendererTypeDefault - htmlString := p.rctx.RenderMarkdown(string(markdownBytes)) - sanitized := p.rctx.SanitizeDefault(htmlString) + rctx := p.rctx.Clone() + rctx.RendererType = markup.RendererTypeDefault + htmlString := rctx.RenderMarkdown(string(markdownBytes)) + sanitized := rctx.SanitizeDefault(htmlString) params.Content = template.HTML(sanitized) return p.execute("legal/privacy", w, params) @@ -796,17 +798,18 @@ if params.KnotUnreachable { return p.executeRepo("repo/knotUnreachable", w, params) } - p.rctx.RepoInfo = params.RepoInfo - p.rctx.RepoInfo.Ref = params.Ref - p.rctx.RendererType = markup.RendererTypeRepoMarkdown + rctx := p.rctx.Clone() + rctx.RepoInfo = params.RepoInfo + rctx.RepoInfo.Ref = params.Ref + rctx.RendererType = markup.RendererTypeRepoMarkdown if params.ReadmeFileName != "" { ext := filepath.Ext(params.ReadmeFileName) switch ext { case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": params.Raw = false - htmlString := p.rctx.RenderMarkdown(params.Readme) - sanitized := p.rctx.SanitizeDefault(htmlString) + htmlString := rctx.RenderMarkdown(params.Readme) + sanitized := rctx.SanitizeDefault(htmlString) params.HTMLReadme = template.HTML(sanitized) default: params.Raw = true @@ -889,17 +892,18 @@ func (p *Pages) RepoTree(w io.Writer, params RepoTreeParams) error { params.Active = "overview" - p.rctx.RepoInfo = params.RepoInfo - p.rctx.RepoInfo.Ref = params.Ref - p.rctx.RendererType = markup.RendererTypeRepoMarkdown + rctx := p.rctx.Clone() + rctx.RepoInfo = params.RepoInfo + rctx.RepoInfo.Ref = params.Ref + rctx.RendererType = markup.RendererTypeRepoMarkdown if params.ReadmeFileName != "" { ext := filepath.Ext(params.ReadmeFileName) switch ext { case ".md", ".markdown", ".mdown", ".mkdn", ".mkd": params.Raw = false - htmlString := p.rctx.RenderMarkdown(params.Readme) - sanitized := p.rctx.SanitizeDefault(htmlString) + htmlString := rctx.RenderMarkdown(params.Readme) + sanitized := rctx.SanitizeDefault(htmlString) params.HTMLReadme = template.HTML(sanitized) default: params.Raw = true @@ -971,11 +975,6 @@ *tangled.RepoBlob_Output } func (p *Pages) RepoBlob(w io.Writer, params RepoBlobParams) error { - switch params.BlobView.ContentType { - case models.BlobContentTypeMarkup: - p.rctx.RepoInfo = params.RepoInfo - } - params.Active = "overview" return p.executeRepo("repo/blob", w, params) } -- tangled.sh