2019-03-16 11:12:44 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-03-16 11:12:44 +08:00
package setting
import (
"regexp"
"strings"
"code.gitea.io/gitea/modules/log"
)
2021-04-20 06:25:08 +08:00
// ExternalMarkupRenderers represents the external markup renderers
2019-03-16 11:12:44 +08:00
var (
2021-07-24 05:21:51 +01:00
ExternalMarkupRenderers [ ] * MarkupRenderer
ExternalSanitizerRules [ ] MarkupSanitizerRule
MermaidMaxSourceCharacters int
2019-03-16 11:12:44 +08:00
)
2022-06-16 11:33:23 +08:00
const (
RenderContentModeSanitized = "sanitized"
RenderContentModeNoSanitizer = "no-sanitizer"
RenderContentModeIframe = "iframe"
)
2023-02-20 00:12:01 +08:00
// Markdown settings
var Markdown = struct {
EnableHardLineBreakInComments bool
EnableHardLineBreakInDocuments bool
CustomURLSchemes [ ] string ` ini:"CUSTOM_URL_SCHEMES" `
FileExtensions [ ] string
EnableMath bool
} {
EnableHardLineBreakInComments : true ,
EnableHardLineBreakInDocuments : false ,
2023-04-26 17:22:54 +02:00
FileExtensions : strings . Split ( ".md,.markdown,.mdown,.mkd,.livemd" , "," ) ,
2023-02-20 00:12:01 +08:00
EnableMath : true ,
}
2021-04-20 06:25:08 +08:00
// MarkupRenderer defines the external parser configured in ini
type MarkupRenderer struct {
2021-06-23 23:09:51 +02:00
Enabled bool
MarkupName string
Command string
FileExtensions [ ] string
IsInputFile bool
NeedPostProcess bool
MarkupSanitizerRules [ ] MarkupSanitizerRule
2022-06-16 11:33:23 +08:00
RenderContentMode string
2019-03-16 11:12:44 +08:00
}
2019-12-07 14:49:04 -05:00
// MarkupSanitizerRule defines the policy for whitelisting attributes on
// certain elements.
type MarkupSanitizerRule struct {
2021-06-23 23:09:51 +02:00
Element string
AllowAttr string
2024-11-18 13:25:42 +08:00
Regexp string
2021-06-23 23:09:51 +02:00
AllowDataURIImages bool
2019-12-07 14:49:04 -05:00
}
2023-02-20 00:12:01 +08:00
func loadMarkupFrom ( rootCfg ConfigProvider ) {
mustMapSetting ( rootCfg , "markdown" , & Markdown )
MermaidMaxSourceCharacters = rootCfg . Section ( "markup" ) . Key ( "MERMAID_MAX_SOURCE_CHARACTERS" ) . MustInt ( 5000 )
2021-06-23 23:09:51 +02:00
ExternalMarkupRenderers = make ( [ ] * MarkupRenderer , 0 , 10 )
2021-06-07 06:50:07 +08:00
ExternalSanitizerRules = make ( [ ] MarkupSanitizerRule , 0 , 10 )
2021-06-23 23:09:51 +02:00
2023-02-20 00:12:01 +08:00
for _ , sec := range rootCfg . Section ( "markup" ) . ChildSections ( ) {
2019-03-16 11:12:44 +08:00
name := strings . TrimPrefix ( sec . Name ( ) , "markup." )
if name == "" {
log . Warn ( "name is empty, markup " + sec . Name ( ) + "ignored" )
continue
}
2020-04-29 07:34:59 -04:00
if name == "sanitizer" || strings . HasPrefix ( name , "sanitizer." ) {
2019-12-07 14:49:04 -05:00
newMarkupSanitizer ( name , sec )
} else {
newMarkupRenderer ( name , sec )
2019-03-16 11:12:44 +08:00
}
2019-12-07 14:49:04 -05:00
}
}
2023-04-25 23:06:39 +08:00
func newMarkupSanitizer ( name string , sec ConfigSection ) {
2021-06-23 23:09:51 +02:00
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 )
2019-12-07 14:49:04 -05:00
}
2021-06-23 23:09:51 +02:00
}
2019-12-07 14:49:04 -05:00
2023-04-25 23:06:39 +08:00
func createMarkupSanitizerRule ( name string , sec ConfigSection ) ( MarkupSanitizerRule , bool ) {
2021-06-23 23:09:51 +02:00
var rule MarkupSanitizerRule
ok := false
if sec . HasKey ( "ALLOW_DATA_URI_IMAGES" ) {
rule . AllowDataURIImages = sec . Key ( "ALLOW_DATA_URI_IMAGES" ) . MustBool ( false )
ok = true
2019-12-07 14:49:04 -05:00
}
2021-06-23 23:09:51 +02:00
if sec . HasKey ( "ELEMENT" ) || sec . HasKey ( "ALLOW_ATTR" ) {
rule . Element = sec . Key ( "ELEMENT" ) . Value ( )
rule . AllowAttr = sec . Key ( "ALLOW_ATTR" ) . Value ( )
2019-12-07 14:49:04 -05:00
2021-06-23 23:09:51 +02:00
if rule . Element == "" || rule . AllowAttr == "" {
log . Error ( "Missing required values from markup.%s. Must have ELEMENT and ALLOW_ATTR defined!" , name )
return rule , false
2020-04-29 07:34:59 -04:00
}
2021-06-23 23:09:51 +02:00
regexpStr := sec . Key ( "REGEXP" ) . Value ( )
if regexpStr != "" {
2024-11-18 13:25:42 +08:00
hasPrefix := strings . HasPrefix ( regexpStr , "^" )
hasSuffix := strings . HasSuffix ( regexpStr , "$" )
if ! hasPrefix || ! hasSuffix {
log . Error ( "In markup.%s: REGEXP must start with ^ and end with $ to be strict" , name )
// to avoid breaking existing user configurations and satisfy the strict requirement in addSanitizerRules
if ! hasPrefix {
regexpStr = "^.*" + regexpStr
}
if ! hasSuffix {
regexpStr += ".*$"
}
}
_ , err := regexp . Compile ( regexpStr )
2021-06-23 23:09:51 +02:00
if err != nil {
log . Error ( "In markup.%s: REGEXP (%s) failed to compile: %v" , name , regexpStr , err )
return rule , false
}
2024-11-18 13:25:42 +08:00
rule . Regexp = regexpStr
2021-06-23 23:09:51 +02:00
}
2019-03-16 11:12:44 +08:00
2021-06-23 23:09:51 +02:00
ok = true
2020-04-29 07:34:59 -04:00
}
2019-03-16 11:12:44 +08:00
2021-06-23 23:09:51 +02:00
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
2019-12-07 14:49:04 -05:00
}
2020-04-29 07:34:59 -04:00
2021-06-23 23:09:51 +02:00
return rule , true
2019-12-07 14:49:04 -05:00
}
2023-04-25 23:06:39 +08:00
func newMarkupRenderer ( name string , sec ConfigSection ) {
2019-12-07 14:49:04 -05:00
extensionReg := regexp . MustCompile ( ` \.\w ` )
extensions := sec . Key ( "FILE_EXTENSIONS" ) . Strings ( "," )
2022-01-20 18:46:10 +01:00
exts := make ( [ ] string , 0 , len ( extensions ) )
2019-12-07 14:49:04 -05:00
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
2019-03-16 11:12:44 +08:00
}
2019-12-07 14:49:04 -05:00
command := sec . Key ( "RENDER_COMMAND" ) . MustString ( "" )
if command == "" {
log . Warn ( " RENDER_COMMAND is empty, markup " + name + " ignored" )
return
}
2022-06-16 11:33:23 +08:00
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
}
2021-06-23 23:09:51 +02:00
ExternalMarkupRenderers = append ( ExternalMarkupRenderers , & MarkupRenderer {
2022-06-16 11:33:23 +08:00
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 ,
2019-12-07 14:49:04 -05:00
} )
2019-03-16 11:12:44 +08:00
}