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 }