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 }