2019-03-16 06:12:44 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-03-16 06:12:44 +03:00
package setting
import (
"regexp"
"strings"
"code.gitea.io/gitea/modules/log"
)
2021-04-20 01:25:08 +03:00
// ExternalMarkupRenderers represents the external markup renderers
2019-03-16 06:12:44 +03:00
var (
2021-07-24 07:21:51 +03:00
ExternalMarkupRenderers [ ] * MarkupRenderer
ExternalSanitizerRules [ ] MarkupSanitizerRule
MermaidMaxSourceCharacters int
2024-03-25 18:05:01 +03:00
FilePreviewMaxLines int
2019-03-16 06:12:44 +03:00
)
2022-06-16 06:33:23 +03:00
const (
RenderContentModeSanitized = "sanitized"
RenderContentModeNoSanitizer = "no-sanitizer"
RenderContentModeIframe = "iframe"
)
2023-02-19 19:12:01 +03: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 18:22:54 +03:00
FileExtensions : strings . Split ( ".md,.markdown,.mdown,.mkd,.livemd" , "," ) ,
2023-02-19 19:12:01 +03:00
EnableMath : true ,
}
2021-04-20 01:25:08 +03:00
// MarkupRenderer defines the external parser configured in ini
type MarkupRenderer struct {
2021-06-24 00:09:51 +03:00
Enabled bool
MarkupName string
Command string
FileExtensions [ ] string
IsInputFile bool
NeedPostProcess bool
MarkupSanitizerRules [ ] MarkupSanitizerRule
2022-06-16 06:33:23 +03:00
RenderContentMode string
2019-03-16 06:12:44 +03:00
}
2019-12-07 22:49:04 +03:00
// MarkupSanitizerRule defines the policy for whitelisting attributes on
// certain elements.
type MarkupSanitizerRule struct {
2021-06-24 00:09:51 +03:00
Element string
AllowAttr string
Regexp * regexp . Regexp
AllowDataURIImages bool
2019-12-07 22:49:04 +03:00
}
2023-02-19 19:12:01 +03:00
func loadMarkupFrom ( rootCfg ConfigProvider ) {
mustMapSetting ( rootCfg , "markdown" , & Markdown )
MermaidMaxSourceCharacters = rootCfg . Section ( "markup" ) . Key ( "MERMAID_MAX_SOURCE_CHARACTERS" ) . MustInt ( 5000 )
2024-03-25 18:05:01 +03:00
FilePreviewMaxLines = rootCfg . Section ( "markup" ) . Key ( "FILEPREVIEW_MAX_LINES" ) . MustInt ( 50 )
2021-06-24 00:09:51 +03:00
ExternalMarkupRenderers = make ( [ ] * MarkupRenderer , 0 , 10 )
2021-06-07 01:50:07 +03:00
ExternalSanitizerRules = make ( [ ] MarkupSanitizerRule , 0 , 10 )
2021-06-24 00:09:51 +03:00
2023-02-19 19:12:01 +03:00
for _ , sec := range rootCfg . Section ( "markup" ) . ChildSections ( ) {
2019-03-16 06:12:44 +03:00
name := strings . TrimPrefix ( sec . Name ( ) , "markup." )
if name == "" {
log . Warn ( "name is empty, markup " + sec . Name ( ) + "ignored" )
continue
}
2020-04-29 14:34:59 +03:00
if name == "sanitizer" || strings . HasPrefix ( name , "sanitizer." ) {
2019-12-07 22:49:04 +03:00
newMarkupSanitizer ( name , sec )
} else {
newMarkupRenderer ( name , sec )
2019-03-16 06:12:44 +03:00
}
2019-12-07 22:49:04 +03:00
}
}
2023-04-25 18:06:39 +03:00
func newMarkupSanitizer ( name string , sec ConfigSection ) {
2021-06-24 00:09:51 +03: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 22:49:04 +03:00
}
2021-06-24 00:09:51 +03:00
}
2019-12-07 22:49:04 +03:00
2023-04-25 18:06:39 +03:00
func createMarkupSanitizerRule ( name string , sec ConfigSection ) ( MarkupSanitizerRule , bool ) {
2021-06-24 00:09:51 +03: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 22:49:04 +03:00
}
2021-06-24 00:09:51 +03: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 22:49:04 +03:00
2021-06-24 00:09:51 +03: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 14:34:59 +03:00
}
2021-06-24 00:09:51 +03:00
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
}
2019-03-16 06:12:44 +03:00
2021-06-24 00:09:51 +03:00
ok = true
2020-04-29 14:34:59 +03:00
}
2019-03-16 06:12:44 +03:00
2021-06-24 00:09:51 +03: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 22:49:04 +03:00
}
2020-04-29 14:34:59 +03:00
2021-06-24 00:09:51 +03:00
return rule , true
2019-12-07 22:49:04 +03:00
}
2023-04-25 18:06:39 +03:00
func newMarkupRenderer ( name string , sec ConfigSection ) {
2019-12-07 22:49:04 +03:00
extensionReg := regexp . MustCompile ( ` \.\w ` )
extensions := sec . Key ( "FILE_EXTENSIONS" ) . Strings ( "," )
2022-01-20 20:46:10 +03:00
exts := make ( [ ] string , 0 , len ( extensions ) )
2019-12-07 22:49:04 +03: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 06:12:44 +03:00
}
2019-12-07 22:49:04 +03:00
command := sec . Key ( "RENDER_COMMAND" ) . MustString ( "" )
if command == "" {
log . Warn ( " RENDER_COMMAND is empty, markup " + name + " ignored" )
return
}
2022-06-16 06:33:23 +03: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-24 00:09:51 +03:00
ExternalMarkupRenderers = append ( ExternalMarkupRenderers , & MarkupRenderer {
2022-06-16 06:33:23 +03: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 22:49:04 +03:00
} )
2019-03-16 06:12:44 +03:00
}