// 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}
// 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 settings
func 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 tools
type Renderer struct {
*setting.MarkupRenderer
}
var (
_ markup.PostProcessRenderer = (*Renderer)(nil)
_ markup.ExternalRenderer = (*Renderer)(nil)
)
// Name returns the external tool name
func (p *Renderer) Name() string {
return p.MarkupName
}
// NeedPostProcess implements markup.Renderer
func (p *Renderer) NeedPostProcess() bool {
return p.MarkupRenderer.NeedPostProcess
}
// Extensions returns the supported extensions of the tool
func (p *Renderer) Extensions() []string {
return p.FileExtensions
}
// SanitizerRules implements markup.Renderer
func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule {
return p.MarkupSanitizerRules
}
// SanitizerDisabled disabled sanitize if return true
func (p *Renderer) SanitizerDisabled() bool {
return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe
}
// DisplayInIFrame represents whether render the content with an iframe
func (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
}