mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-10-24 11:02:42 +00:00
Backport: https://codeberg.org/forgejo/forgejo/pulls/2669 (cherry picked from commit1d3240887c) (cherry picked from commit781a37fbe1) (cherry picked from commit8309f008c2) (cherry picked from commitfae8d9f70d) (cherry picked from commit6721cba75b) (cherry picked from commit562e5cdf32) (cherry picked from commitd789d33229) (cherry picked from commit8218e80bfc) (cherry picked from commit10bca456a9) (cherry picked from commitdb6f6281fc) (cherry picked from commited8e8a792e) (cherry picked from commitd6428f92ce) (cherry picked from commit069d87b80f) (cherry picked from commit2b6546adc9) (cherry picked from commit4c7cb0a5d2) (cherry picked from commit7e0014dd13) (cherry picked from commit16a8658878) (cherry picked from commit6e98bacbbd)
192 lines
5.9 KiB
Go
192 lines
5.9 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
|
)
|
|
|
|
// ExternalMarkupRenderers represents the external markup renderers
|
|
var (
|
|
ExternalMarkupRenderers []*MarkupRenderer
|
|
ExternalSanitizerRules []MarkupSanitizerRule
|
|
MermaidMaxSourceCharacters int
|
|
FilePreviewMaxLines int
|
|
)
|
|
|
|
const (
|
|
RenderContentModeSanitized = "sanitized"
|
|
RenderContentModeNoSanitizer = "no-sanitizer"
|
|
RenderContentModeIframe = "iframe"
|
|
)
|
|
|
|
// Markdown settings
|
|
var Markdown = struct {
|
|
EnableHardLineBreakInComments bool
|
|
EnableHardLineBreakInDocuments bool
|
|
CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"`
|
|
FileExtensions []string
|
|
EnableMath bool
|
|
}{
|
|
EnableHardLineBreakInComments: true,
|
|
EnableHardLineBreakInDocuments: false,
|
|
FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","),
|
|
EnableMath: true,
|
|
}
|
|
|
|
// MarkupRenderer defines the external parser configured in ini
|
|
type MarkupRenderer struct {
|
|
Enabled bool
|
|
MarkupName string
|
|
Command string
|
|
FileExtensions []string
|
|
IsInputFile bool
|
|
NeedPostProcess bool
|
|
MarkupSanitizerRules []MarkupSanitizerRule
|
|
RenderContentMode string
|
|
}
|
|
|
|
// MarkupSanitizerRule defines the policy for whitelisting attributes on
|
|
// certain elements.
|
|
type MarkupSanitizerRule struct {
|
|
Element string
|
|
AllowAttr string
|
|
Regexp *regexp.Regexp
|
|
AllowDataURIImages bool
|
|
}
|
|
|
|
func loadMarkupFrom(rootCfg ConfigProvider) {
|
|
mustMapSetting(rootCfg, "markdown", &Markdown)
|
|
|
|
MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
|
|
FilePreviewMaxLines = rootCfg.Section("markup").Key("FILEPREVIEW_MAX_LINES").MustInt(50)
|
|
ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
|
|
ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
|
|
|
|
for _, sec := range rootCfg.Section("markup").ChildSections() {
|
|
name := strings.TrimPrefix(sec.Name(), "markup.")
|
|
if name == "" {
|
|
log.Warn("name is empty, markup " + sec.Name() + "ignored")
|
|
continue
|
|
}
|
|
|
|
if name == "sanitizer" || strings.HasPrefix(name, "sanitizer.") {
|
|
newMarkupSanitizer(name, sec)
|
|
} else {
|
|
newMarkupRenderer(name, sec)
|
|
}
|
|
}
|
|
}
|
|
|
|
func newMarkupSanitizer(name string, sec ConfigSection) {
|
|
rule, ok := createMarkupSanitizerRule(name, sec)
|
|
if ok {
|
|
if strings.HasPrefix(name, "sanitizer.") {
|
|
names := strings.SplitN(strings.TrimPrefix(name, "sanitizer."), ".", 2)
|
|
name = names[0]
|
|
}
|
|
for _, renderer := range ExternalMarkupRenderers {
|
|
if name == renderer.MarkupName {
|
|
renderer.MarkupSanitizerRules = append(renderer.MarkupSanitizerRules, rule)
|
|
return
|
|
}
|
|
}
|
|
ExternalSanitizerRules = append(ExternalSanitizerRules, rule)
|
|
}
|
|
}
|
|
|
|
func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerRule, bool) {
|
|
var rule MarkupSanitizerRule
|
|
|
|
ok := false
|
|
if sec.HasKey("ALLOW_DATA_URI_IMAGES") {
|
|
rule.AllowDataURIImages = sec.Key("ALLOW_DATA_URI_IMAGES").MustBool(false)
|
|
ok = true
|
|
}
|
|
|
|
if sec.HasKey("ELEMENT") || sec.HasKey("ALLOW_ATTR") {
|
|
rule.Element = sec.Key("ELEMENT").Value()
|
|
rule.AllowAttr = sec.Key("ALLOW_ATTR").Value()
|
|
|
|
if rule.Element == "" || rule.AllowAttr == "" {
|
|
log.Error("Missing required values from markup.%s. Must have ELEMENT and ALLOW_ATTR defined!", name)
|
|
return rule, false
|
|
}
|
|
|
|
regexpStr := sec.Key("REGEXP").Value()
|
|
if regexpStr != "" {
|
|
// Validate when parsing the config that this is a valid regular
|
|
// expression. Then we can use regexp.MustCompile(...) later.
|
|
compiled, err := regexp.Compile(regexpStr)
|
|
if err != nil {
|
|
log.Error("In markup.%s: REGEXP (%s) failed to compile: %v", name, regexpStr, err)
|
|
return rule, false
|
|
}
|
|
|
|
rule.Regexp = compiled
|
|
}
|
|
|
|
ok = true
|
|
}
|
|
|
|
if !ok {
|
|
log.Error("Missing required keys from markup.%s. Must have ELEMENT and ALLOW_ATTR or ALLOW_DATA_URI_IMAGES defined!", name)
|
|
return rule, false
|
|
}
|
|
|
|
return rule, true
|
|
}
|
|
|
|
func newMarkupRenderer(name string, sec ConfigSection) {
|
|
extensionReg := regexp.MustCompile(`\.\w`)
|
|
|
|
extensions := sec.Key("FILE_EXTENSIONS").Strings(",")
|
|
exts := make([]string, 0, len(extensions))
|
|
for _, extension := range extensions {
|
|
if !extensionReg.MatchString(extension) {
|
|
log.Warn(sec.Name() + " file extension " + extension + " is invalid. Extension ignored")
|
|
} else {
|
|
exts = append(exts, extension)
|
|
}
|
|
}
|
|
|
|
if len(exts) == 0 {
|
|
log.Warn(sec.Name() + " file extension is empty, markup " + name + " ignored")
|
|
return
|
|
}
|
|
|
|
command := sec.Key("RENDER_COMMAND").MustString("")
|
|
if command == "" {
|
|
log.Warn(" RENDER_COMMAND is empty, markup " + name + " ignored")
|
|
return
|
|
}
|
|
|
|
if sec.HasKey("DISABLE_SANITIZER") {
|
|
log.Error("Deprecated setting `[markup.*]` `DISABLE_SANITIZER` present. This fallback will be removed in v1.18.0")
|
|
}
|
|
|
|
renderContentMode := sec.Key("RENDER_CONTENT_MODE").MustString(RenderContentModeSanitized)
|
|
if !sec.HasKey("RENDER_CONTENT_MODE") && sec.Key("DISABLE_SANITIZER").MustBool(false) {
|
|
renderContentMode = RenderContentModeNoSanitizer // if only the legacy DISABLE_SANITIZER exists, use it
|
|
}
|
|
if renderContentMode != RenderContentModeSanitized &&
|
|
renderContentMode != RenderContentModeNoSanitizer &&
|
|
renderContentMode != RenderContentModeIframe {
|
|
log.Error("invalid RENDER_CONTENT_MODE: %q, default to %q", renderContentMode, RenderContentModeSanitized)
|
|
renderContentMode = RenderContentModeSanitized
|
|
}
|
|
|
|
ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
|
|
Enabled: sec.Key("ENABLED").MustBool(false),
|
|
MarkupName: name,
|
|
FileExtensions: exts,
|
|
Command: command,
|
|
IsInputFile: sec.Key("IS_INPUT_FILE").MustBool(false),
|
|
NeedPostProcess: sec.Key("NEED_POSTPROCESS").MustBool(true),
|
|
RenderContentMode: renderContentMode,
|
|
})
|
|
}
|