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