142 lines · 3.9 KB · Go
RawDownload
// Copyright 2017 The Gitea Authors. All rights reserved.// SPDX-License-Identifier: MIT package external import (	"bytes"	"fmt"	"io"	"os"	"os/exec"	"strings" 	"forgejo.org/modules/graceful"	"forgejo.org/modules/log"	"forgejo.org/modules/markup"	"forgejo.org/modules/process"	"forgejo.org/modules/setting"	"forgejo.org/modules/util") // RegisterRenderers registers all supported third part renderers according settingsfunc RegisterRenderers() {	for _, renderer := range setting.ExternalMarkupRenderers {		if renderer.Enabled && renderer.Command != "" && len(renderer.FileExtensions) > 0 {			markup.RegisterRenderer(&Renderer{renderer})		}	}} // Renderer implements markup.Renderer for external toolstype Renderer struct {	*setting.MarkupRenderer} var (	_ markup.PostProcessRenderer = (*Renderer)(nil)	_ markup.ExternalRenderer    = (*Renderer)(nil)) // Name returns the external tool namefunc (p *Renderer) Name() string {	return p.MarkupName} // NeedPostProcess implements markup.Rendererfunc (p *Renderer) NeedPostProcess() bool {	return p.MarkupRenderer.NeedPostProcess} // Extensions returns the supported extensions of the toolfunc (p *Renderer) Extensions() []string {	return p.FileExtensions} // SanitizerRules implements markup.Rendererfunc (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {	return p.MarkupSanitizerRules} // SanitizerDisabled disabled sanitize if return truefunc (p *Renderer) SanitizerDisabled() bool {	return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe} // DisplayInIFrame represents whether render the content with an iframefunc (p *Renderer) DisplayInIFrame() bool {	return p.RenderContentMode == setting.RenderContentModeIframe} func envMark(envName string) string {	return "$" + envName} // Render renders the data of the document to HTML via the external tool.func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {	var (		command = strings.NewReplacer(			envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(),			envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(),		).Replace(p.Command)		commands = strings.Fields(command)		args     = commands[1:]	) 	if p.IsInputFile {		// write to temp file		f, err := os.CreateTemp("", "gitea_input")		if err != nil {			return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err)		}		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, input)		if err != nil {			f.Close()			return fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err)		} 		err = f.Close()		if err != nil {			return fmt.Errorf("%s close temp file when rendering %s failed: %w", p.Name(), p.Command, err)		}		args = append(args, f.Name())	} 	if ctx == nil || ctx.Ctx == nil {		if ctx == nil {			log.Warn("RenderContext not provided defaulting to empty ctx")			ctx = &markup.RenderContext{}		}		log.Warn("RenderContext did not provide context, defaulting to Shutdown context")		ctx.Ctx = graceful.GetManager().ShutdownContext()	} 	processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink()))	defer finished() 	cmd := exec.CommandContext(processCtx, commands[0], args...)	cmd.Env = append(		os.Environ(),		"GITEA_PREFIX_SRC="+ctx.Links.SrcLink(),		"GITEA_PREFIX_RAW="+ctx.Links.RawLink(),	)	if !p.IsInputFile {		cmd.Stdin = input	}	var stderr bytes.Buffer	cmd.Stdout = output	cmd.Stderr = &stderr	process.SetSysProcAttribute(cmd) 	if err := cmd.Run(); err != nil {		return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String())	}	return nil}