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