code.gitea.io/gitea@v1.22.3/modules/setting/markup.go (about)

     1  // Copyright 2019 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"regexp"
     8  	"strings"
     9  
    10  	"code.gitea.io/gitea/modules/log"
    11  )
    12  
    13  // ExternalMarkupRenderers represents the external markup renderers
    14  var (
    15  	ExternalMarkupRenderers    []*MarkupRenderer
    16  	ExternalSanitizerRules     []MarkupSanitizerRule
    17  	MermaidMaxSourceCharacters int
    18  )
    19  
    20  const (
    21  	RenderContentModeSanitized   = "sanitized"
    22  	RenderContentModeNoSanitizer = "no-sanitizer"
    23  	RenderContentModeIframe      = "iframe"
    24  )
    25  
    26  // Markdown settings
    27  var Markdown = struct {
    28  	EnableHardLineBreakInComments  bool
    29  	EnableHardLineBreakInDocuments bool
    30  	CustomURLSchemes               []string `ini:"CUSTOM_URL_SCHEMES"`
    31  	FileExtensions                 []string
    32  	EnableMath                     bool
    33  }{
    34  	EnableHardLineBreakInComments:  true,
    35  	EnableHardLineBreakInDocuments: false,
    36  	FileExtensions:                 strings.Split(".md,.markdown,.mdown,.mkd,.livemd", ","),
    37  	EnableMath:                     true,
    38  }
    39  
    40  // MarkupRenderer defines the external parser configured in ini
    41  type MarkupRenderer struct {
    42  	Enabled              bool
    43  	MarkupName           string
    44  	Command              string
    45  	FileExtensions       []string
    46  	IsInputFile          bool
    47  	NeedPostProcess      bool
    48  	MarkupSanitizerRules []MarkupSanitizerRule
    49  	RenderContentMode    string
    50  }
    51  
    52  // MarkupSanitizerRule defines the policy for whitelisting attributes on
    53  // certain elements.
    54  type MarkupSanitizerRule struct {
    55  	Element            string
    56  	AllowAttr          string
    57  	Regexp             *regexp.Regexp
    58  	AllowDataURIImages bool
    59  }
    60  
    61  func loadMarkupFrom(rootCfg ConfigProvider) {
    62  	mustMapSetting(rootCfg, "markdown", &Markdown)
    63  
    64  	MermaidMaxSourceCharacters = rootCfg.Section("markup").Key("MERMAID_MAX_SOURCE_CHARACTERS").MustInt(5000)
    65  	ExternalMarkupRenderers = make([]*MarkupRenderer, 0, 10)
    66  	ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, 10)
    67  
    68  	for _, sec := range rootCfg.Section("markup").ChildSections() {
    69  		name := strings.TrimPrefix(sec.Name(), "markup.")
    70  		if name == "" {
    71  			log.Warn("name is empty, markup " + sec.Name() + "ignored")
    72  			continue
    73  		}
    74  
    75  		if name == "sanitizer" || strings.HasPrefix(name, "sanitizer.") {
    76  			newMarkupSanitizer(name, sec)
    77  		} else {
    78  			newMarkupRenderer(name, sec)
    79  		}
    80  	}
    81  }
    82  
    83  func newMarkupSanitizer(name string, sec ConfigSection) {
    84  	rule, ok := createMarkupSanitizerRule(name, sec)
    85  	if ok {
    86  		if strings.HasPrefix(name, "sanitizer.") {
    87  			names := strings.SplitN(strings.TrimPrefix(name, "sanitizer."), ".", 2)
    88  			name = names[0]
    89  		}
    90  		for _, renderer := range ExternalMarkupRenderers {
    91  			if name == renderer.MarkupName {
    92  				renderer.MarkupSanitizerRules = append(renderer.MarkupSanitizerRules, rule)
    93  				return
    94  			}
    95  		}
    96  		ExternalSanitizerRules = append(ExternalSanitizerRules, rule)
    97  	}
    98  }
    99  
   100  func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerRule, bool) {
   101  	var rule MarkupSanitizerRule
   102  
   103  	ok := false
   104  	if sec.HasKey("ALLOW_DATA_URI_IMAGES") {
   105  		rule.AllowDataURIImages = sec.Key("ALLOW_DATA_URI_IMAGES").MustBool(false)
   106  		ok = true
   107  	}
   108  
   109  	if sec.HasKey("ELEMENT") || sec.HasKey("ALLOW_ATTR") {
   110  		rule.Element = sec.Key("ELEMENT").Value()
   111  		rule.AllowAttr = sec.Key("ALLOW_ATTR").Value()
   112  
   113  		if rule.Element == "" || rule.AllowAttr == "" {
   114  			log.Error("Missing required values from markup.%s. Must have ELEMENT and ALLOW_ATTR defined!", name)
   115  			return rule, false
   116  		}
   117  
   118  		regexpStr := sec.Key("REGEXP").Value()
   119  		if regexpStr != "" {
   120  			// Validate when parsing the config that this is a valid regular
   121  			// expression. Then we can use regexp.MustCompile(...) later.
   122  			compiled, err := regexp.Compile(regexpStr)
   123  			if err != nil {
   124  				log.Error("In markup.%s: REGEXP (%s) failed to compile: %v", name, regexpStr, err)
   125  				return rule, false
   126  			}
   127  
   128  			rule.Regexp = compiled
   129  		}
   130  
   131  		ok = true
   132  	}
   133  
   134  	if !ok {
   135  		log.Error("Missing required keys from markup.%s. Must have ELEMENT and ALLOW_ATTR or ALLOW_DATA_URI_IMAGES defined!", name)
   136  		return rule, false
   137  	}
   138  
   139  	return rule, true
   140  }
   141  
   142  func newMarkupRenderer(name string, sec ConfigSection) {
   143  	extensionReg := regexp.MustCompile(`\.\w`)
   144  
   145  	extensions := sec.Key("FILE_EXTENSIONS").Strings(",")
   146  	exts := make([]string, 0, len(extensions))
   147  	for _, extension := range extensions {
   148  		if !extensionReg.MatchString(extension) {
   149  			log.Warn(sec.Name() + " file extension " + extension + " is invalid. Extension ignored")
   150  		} else {
   151  			exts = append(exts, extension)
   152  		}
   153  	}
   154  
   155  	if len(exts) == 0 {
   156  		log.Warn(sec.Name() + " file extension is empty, markup " + name + " ignored")
   157  		return
   158  	}
   159  
   160  	command := sec.Key("RENDER_COMMAND").MustString("")
   161  	if command == "" {
   162  		log.Warn(" RENDER_COMMAND is empty, markup " + name + " ignored")
   163  		return
   164  	}
   165  
   166  	if sec.HasKey("DISABLE_SANITIZER") {
   167  		log.Error("Deprecated setting `[markup.*]` `DISABLE_SANITIZER` present. This fallback will be removed in v1.18.0")
   168  	}
   169  
   170  	renderContentMode := sec.Key("RENDER_CONTENT_MODE").MustString(RenderContentModeSanitized)
   171  	if !sec.HasKey("RENDER_CONTENT_MODE") && sec.Key("DISABLE_SANITIZER").MustBool(false) {
   172  		renderContentMode = RenderContentModeNoSanitizer // if only the legacy DISABLE_SANITIZER exists, use it
   173  	}
   174  	if renderContentMode != RenderContentModeSanitized &&
   175  		renderContentMode != RenderContentModeNoSanitizer &&
   176  		renderContentMode != RenderContentModeIframe {
   177  		log.Error("invalid RENDER_CONTENT_MODE: %q, default to %q", renderContentMode, RenderContentModeSanitized)
   178  		renderContentMode = RenderContentModeSanitized
   179  	}
   180  
   181  	ExternalMarkupRenderers = append(ExternalMarkupRenderers, &MarkupRenderer{
   182  		Enabled:           sec.Key("ENABLED").MustBool(false),
   183  		MarkupName:        name,
   184  		FileExtensions:    exts,
   185  		Command:           command,
   186  		IsInputFile:       sec.Key("IS_INPUT_FILE").MustBool(false),
   187  		NeedPostProcess:   sec.Key("NEED_POSTPROCESS").MustBool(true),
   188  		RenderContentMode: renderContentMode,
   189  	})
   190  }