// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package setting

import (
	"regexp"
	"strings"

	"code.gitea.io/gitea/modules/log"

	"gopkg.in/ini.v1"
)

// ExternalMarkupParsers represents the external markup parsers
var (
	ExternalMarkupParsers  []MarkupParser
	ExternalSanitizerRules []MarkupSanitizerRule
)

// MarkupParser defines the external parser configured in ini
type MarkupParser struct {
	Enabled        bool
	MarkupName     string
	Command        string
	FileExtensions []string
	IsInputFile    bool
}

// MarkupSanitizerRule defines the policy for whitelisting attributes on
// certain elements.
type MarkupSanitizerRule struct {
	Element   string
	AllowAttr string
	Regexp    *regexp.Regexp
}

func newMarkup() {
	for _, sec := range Cfg.Section("markup").ChildSections() {
		name := strings.TrimPrefix(sec.Name(), "markup.")
		if name == "" {
			log.Warn("name is empty, markup " + sec.Name() + "ignored")
			continue
		}

		if name == "sanitizer" {
			newMarkupSanitizer(name, sec)
		} else {
			newMarkupRenderer(name, sec)
		}
	}
}

func newMarkupSanitizer(name string, sec *ini.Section) {
	haveElement := sec.HasKey("ELEMENT")
	haveAttr := sec.HasKey("ALLOW_ATTR")
	haveRegexp := sec.HasKey("REGEXP")

	if !haveElement && !haveAttr && !haveRegexp {
		log.Warn("Skipping empty section: markup.%s.", name)
		return
	}

	if !haveElement || !haveAttr || !haveRegexp {
		log.Error("Missing required keys from markup.%s. Must have all three of ELEMENT, ALLOW_ATTR, and REGEXP defined!", name)
		return
	}

	elements := sec.Key("ELEMENT").ValueWithShadows()
	allowAttrs := sec.Key("ALLOW_ATTR").ValueWithShadows()
	regexps := sec.Key("REGEXP").ValueWithShadows()

	if len(elements) != len(allowAttrs) ||
		len(elements) != len(regexps) {
		log.Error("All three keys in markup.%s (ELEMENT, ALLOW_ATTR, REGEXP) must be defined the same number of times! Got %d, %d, and %d respectively.", name, len(elements), len(allowAttrs), len(regexps))
		return
	}

	ExternalSanitizerRules = make([]MarkupSanitizerRule, 0, len(elements))

	for index, pattern := range regexps {
		if pattern == "" {
			rule := MarkupSanitizerRule{
				Element:   elements[index],
				AllowAttr: allowAttrs[index],
				Regexp:    nil,
			}
			ExternalSanitizerRules = append(ExternalSanitizerRules, rule)
			continue
		}

		// Validate when parsing the config that this is a valid regular
		// expression. Then we can use regexp.MustCompile(...) later.
		compiled, err := regexp.Compile(pattern)
		if err != nil {
			log.Error("In module.%s: REGEXP at definition %d failed to compile: %v", name, index+1, err)
			continue
		}

		rule := MarkupSanitizerRule{
			Element:   elements[index],
			AllowAttr: allowAttrs[index],
			Regexp:    compiled,
		}
		ExternalSanitizerRules = append(ExternalSanitizerRules, rule)
	}
}

func newMarkupRenderer(name string, sec *ini.Section) {
	extensionReg := regexp.MustCompile(`\.\w`)

	extensions := sec.Key("FILE_EXTENSIONS").Strings(",")
	var exts = make([]string, 0, len(extensions))
	for _, extension := range extensions {
		if !extensionReg.MatchString(extension) {
			log.Warn(sec.Name() + " file extension " + extension + " is invalid. Extension ignored")
		} else {
			exts = append(exts, extension)
		}
	}

	if len(exts) == 0 {
		log.Warn(sec.Name() + " file extension is empty, markup " + name + " ignored")
		return
	}

	command := sec.Key("RENDER_COMMAND").MustString("")
	if command == "" {
		log.Warn(" RENDER_COMMAND is empty, markup " + name + " ignored")
		return
	}

	ExternalMarkupParsers = append(ExternalMarkupParsers, MarkupParser{
		Enabled:        sec.Key("ENABLED").MustBool(false),
		MarkupName:     name,
		FileExtensions: exts,
		Command:        command,
		IsInputFile:    sec.Key("IS_INPUT_FILE").MustBool(false),
	})
}