forgejo/modules/setting/markup.go
Mai-Lapyst 22aedc6c96
[v7.0/forgejo] Render inline file permalinks
Backport: https://codeberg.org/forgejo/forgejo/pulls/2669

(cherry picked from commit 1d3240887c519a04c13bcd7e852c6d6ad1cb00b5)
(cherry picked from commit 781a37fbe18c223763f51968862f1c8f61e7e260)
(cherry picked from commit 8309f008c2721e313e1949ce42ed410e844c16e7)
(cherry picked from commit fae8d9f70d31704af91cbf37bcefcc4772830695)
(cherry picked from commit 6721cba75b4997448b618a4b00ef25f142924de0)
(cherry picked from commit 562e5cdf324597882b7e6971be1b9a148bbc7839)
(cherry picked from commit d789d33229b3998bb33f1505d122504c8039f23d)
(cherry picked from commit 8218e80bfc3a1f9ba02ce60f1acafdc0e57c5ae0)
(cherry picked from commit 10bca456a9140519e95559aa7bac2221e1156c5b)
(cherry picked from commit db6f6281fcf568ae8e35330a4a93c9be1cb46efd)
(cherry picked from commit ed8e8a792e75b930074cd3cf1bab580a09ff8485)
(cherry picked from commit d6428f92ce7ce67d127cbd5bb4977aa92abf071c)
(cherry picked from commit 069d87b80f909e91626249afbb240a1df339a8fd)
(cherry picked from commit 2b6546adc954d450a9c6befccd407ce2ca1636a0)
(cherry picked from commit 4c7cb0a5d20e8973b03e35d91119cf917eed125e)
(cherry picked from commit 7e0014dd1391e123d95f2537c3b2165fef7122ef)
(cherry picked from commit 16a8658878a2656cb131453b728b65a89271f11f)
(cherry picked from commit 6e98bacbbd3c089b2ccfa725c58184f4dfe5e7fe)
2024-04-01 16:15:58 +02:00

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,
})
}