// Copyright 2017 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 external
import (
"bytes"
"io"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// RegisterParsers registers all supported third part parsers according settings
func RegisterParsers() {
for _, parser := range setting.ExternalMarkupParsers {
if parser.Enabled && parser.Command != "" && len(parser.FileExtensions) > 0 {
markup.RegisterParser(&Parser{parser})
}
// Parser implements markup.Parser for external tools
type Parser struct {
setting.MarkupParser
// Name returns the external tool name
func (p *Parser) Name() string {
return p.MarkupName
// NeedPostProcess implements markup.Parser
func (p *Parser) NeedPostProcess() bool {
return p.MarkupParser.NeedPostProcess
// Extensions returns the supported extensions of the tool
func (p *Parser) Extensions() []string {
return p.FileExtensions
func envMark(envName string) string {
if runtime.GOOS == "windows" {
return "%" + envName + "%"
return "$" + envName
// Render renders the data of the document to HTML via the external tool.
func (p *Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
var (
bs []byte
buf = bytes.NewBuffer(bs)
rd = bytes.NewReader(rawBytes)
urlRawPrefix = strings.Replace(urlPrefix, "/src/", "/raw/", 1)
command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), urlPrefix,
envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command)
commands = strings.Fields(command)
args = commands[1:]
if p.IsInputFile {
// write to temp file
f, err := ioutil.TempFile("", "gitea_input")
if err != nil {
log.Error("%s create temp file when rendering %s failed: %v", p.Name(), p.Command, err)
return []byte("")
tmpPath := f.Name()
defer func() {
if err := util.Remove(tmpPath); err != nil {
log.Warn("Unable to remove temporary file: %s: Error: %v", tmpPath, err)
}()
_, err = io.Copy(f, rd)
f.Close()
log.Error("%s write data to temp file when rendering %s failed: %v", p.Name(), p.Command, err)
err = f.Close()
log.Error("%s close temp file when rendering %s failed: %v", p.Name(), p.Command, err)
args = append(args, f.Name())
cmd := exec.Command(commands[0], args...)
cmd.Env = append(
os.Environ(),
"GITEA_PREFIX_SRC="+urlPrefix,
"GITEA_PREFIX_RAW="+urlRawPrefix,
if !p.IsInputFile {
cmd.Stdin = rd
cmd.Stdout = buf
if err := cmd.Run(); err != nil {
log.Error("%s render run command %s %v failed: %v", p.Name(), commands[0], args, err)
return buf.Bytes()