mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
150 lines
5.9 KiB
Go
150 lines
5.9 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package markup
|
|
|
|
import (
|
|
"html/template"
|
|
"io"
|
|
"net/url"
|
|
"regexp"
|
|
|
|
"github.com/hanzoai/git/modules/setting"
|
|
|
|
"github.com/microcosm-cc/bluemonday"
|
|
)
|
|
|
|
func (st *Sanitizer) createDefaultPolicy() *bluemonday.Policy {
|
|
policy := bluemonday.UGCPolicy()
|
|
|
|
// NOTICE: DO NOT add special "class" regexp rules here anymore, use RenderInternal.SafeAttr instead
|
|
|
|
// General safe SVG attributes
|
|
policy.AllowAttrs("viewBox", "width", "height", "aria-hidden", "data-attr-class").OnElements("svg")
|
|
policy.AllowAttrs("fill-rule", "d").OnElements("path")
|
|
|
|
// Checkboxes
|
|
policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
|
policy.AllowAttrs("checked", "disabled", "data-source-position").OnElements("input")
|
|
|
|
// Chroma always uses 1-2 letters for style names, we could tolerate it at the moment
|
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^\w{0,2}$`)).OnElements("span")
|
|
|
|
// Line numbers on codepreview
|
|
policy.AllowAttrs("data-line-number").OnElements("span")
|
|
|
|
// Custom URL-Schemes
|
|
if len(setting.Markdown.CustomURLSchemes) > 0 {
|
|
policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
|
} else {
|
|
policy.AllowURLSchemesMatching(st.allowAllRegex)
|
|
|
|
// Even if every scheme is allowed, these three are blocked for security reasons
|
|
disallowScheme := func(*url.URL) bool {
|
|
return false
|
|
}
|
|
policy.AllowURLSchemeWithCustomPolicy("javascript", disallowScheme)
|
|
policy.AllowURLSchemeWithCustomPolicy("vbscript", disallowScheme)
|
|
policy.AllowURLSchemeWithCustomPolicy("data", disallowScheme)
|
|
}
|
|
|
|
// Allow classes for org mode list item status.
|
|
policy.AllowAttrs("class").Matching(regexp.MustCompile(`^(unchecked|checked|indeterminate)$`)).OnElements("li")
|
|
|
|
// Allow 'color' and 'background-color' properties for the style attribute on text elements.
|
|
policy.AllowStyles("color", "background-color").OnElements("div", "span", "p", "tr", "th", "td")
|
|
|
|
policy.AllowAttrs("src", "autoplay", "controls", "muted", "loop", "playsinline").OnElements("video")
|
|
|
|
// Native support of "<picture><source media=... srcset=...><img src=...></picture>"
|
|
// ATTENTION: it only works with "auto" theme, because "media" query doesn't work with the theme chosen by end user manually.
|
|
// For example: browser's color scheme is "dark", but end user chooses "light" theme. Maybe it needs JS to help to make it work.
|
|
policy.AllowAttrs("media", "srcset").OnElements("source")
|
|
|
|
policy.AllowAttrs("loading").OnElements("img")
|
|
|
|
// MathML Core (https://www.w3.org/TR/mathml-core/)
|
|
mathMLElements := []string{
|
|
"math",
|
|
// token elements
|
|
"mi", "mn", "mo", "mtext", "mspace", "ms",
|
|
// layout elements
|
|
"mrow", "mfrac", "msqrt", "mroot", "mstyle", "merror", "mpadded", "mphantom",
|
|
// scripting elements
|
|
"msub", "msup", "msubsup", "munder", "mover", "munderover", "mmultiscripts", "mprescripts", "none",
|
|
// tabular elements
|
|
"mtable", "mtr", "mtd",
|
|
// semantic annotations
|
|
"semantics", "annotation", "annotation-xml",
|
|
}
|
|
policy.AllowAttrs("display", "alttext").OnElements("math")
|
|
policy.AllowAttrs(
|
|
// global presentation attributes
|
|
"dir", "displaystyle", "mathbackground", "mathcolor", "mathsize", "mathvariant", "scriptlevel",
|
|
// operator attributes
|
|
"accent", "accentunder", "fence", "form", "largeop", "lspace", "maxsize", "minsize", "movablelimits", "rspace", "separator", "stretchy", "symmetric",
|
|
// space and padding attributes
|
|
"depth", "height", "voffset", "width",
|
|
// fraction attribute
|
|
"linethickness",
|
|
// table attributes
|
|
"columnalign", "columnlines", "columnspacing", "frame", "framespacing", "rowalign", "rowlines", "rowspacing",
|
|
// cell attributes
|
|
"columnspan",
|
|
// annotation attribute
|
|
"encoding",
|
|
).OnElements(mathMLElements...)
|
|
|
|
// Allow generally safe attributes (reference: https://github.com/jch/html-pipeline)
|
|
generalSafeAttrs := []string{
|
|
"abbr", "accept", "accept-charset",
|
|
"accesskey", "action", "align", "alt",
|
|
"aria-describedby", "aria-hidden", "aria-label", "aria-labelledby",
|
|
"axis", "border", "cellpadding", "cellspacing", "char",
|
|
"charoff", "charset", "checked",
|
|
"clear", "cols", "colspan", "color",
|
|
"compact", "coords", "datetime", "dir",
|
|
"disabled", "enctype", "for", "frame",
|
|
"headers", "height", "hreflang",
|
|
"hspace", "ismap", "label", "lang",
|
|
"maxlength", "media", "method",
|
|
"multiple", "name", "nohref", "noshade",
|
|
"nowrap", "open", "prompt", "readonly", "rel", "rev",
|
|
"rows", "rowspan", "rules", "scope",
|
|
"selected", "shape", "size", "span",
|
|
"start", "summary", "tabindex", "target",
|
|
"title", "type", "usemap", "valign", "value",
|
|
"vspace", "width", "itemprop", "itemscope", "itemtype",
|
|
"data-markdown-generated-content", "data-attr-class",
|
|
}
|
|
generalSafeElements := []string{
|
|
"h1", "h2", "h3", "h4", "h5", "h6", "h7", "h8", "br", "b", "center", "i", "strong", "em", "a", "pre", "code", "img", "tt",
|
|
"div", "ins", "del", "sup", "sub", "p", "ol", "ul", "table", "thead", "tbody", "tfoot", "blockquote", "label",
|
|
"dl", "dt", "dd", "kbd", "q", "samp", "var", "hr", "ruby", "rt", "rp", "li", "tr", "td", "th", "s", "strike", "summary",
|
|
"details", "caption", "figure", "figcaption",
|
|
"abbr", "bdo", "cite", "dfn", "mark", "small", "span", "time", "video", "wbr",
|
|
"picture", "source",
|
|
}
|
|
// FIXME: Need to handle longdesc in img but there is no easy way to do it
|
|
policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...)
|
|
|
|
// Custom keyword markup
|
|
defaultSanitizer.addSanitizerRules(policy, setting.ExternalSanitizerRules)
|
|
|
|
return policy
|
|
}
|
|
|
|
// Sanitize use default sanitizer policy to sanitize a string
|
|
func Sanitize(s string) template.HTML {
|
|
return template.HTML(GetDefaultSanitizer().defaultPolicy.Sanitize(s))
|
|
}
|
|
|
|
// SanitizeReader sanitizes a Reader
|
|
func SanitizeReader(r io.Reader, renderer string, w io.Writer) error {
|
|
policy, exist := GetDefaultSanitizer().rendererPolicies[renderer]
|
|
if !exist {
|
|
policy = GetDefaultSanitizer().defaultPolicy
|
|
}
|
|
return policy.SanitizeReaderToWriter(r, w)
|
|
}
|