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  }