code.gitea.io/gitea@v1.22.3/modules/markup/external/external.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package external 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "os" 11 "os/exec" 12 "runtime" 13 "strings" 14 15 "code.gitea.io/gitea/modules/graceful" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/markup" 18 "code.gitea.io/gitea/modules/process" 19 "code.gitea.io/gitea/modules/setting" 20 "code.gitea.io/gitea/modules/util" 21 ) 22 23 // RegisterRenderers registers all supported third part renderers according settings 24 func RegisterRenderers() { 25 for _, renderer := range setting.ExternalMarkupRenderers { 26 if renderer.Enabled && renderer.Command != "" && len(renderer.FileExtensions) > 0 { 27 markup.RegisterRenderer(&Renderer{renderer}) 28 } 29 } 30 } 31 32 // Renderer implements markup.Renderer for external tools 33 type Renderer struct { 34 *setting.MarkupRenderer 35 } 36 37 var ( 38 _ markup.PostProcessRenderer = (*Renderer)(nil) 39 _ markup.ExternalRenderer = (*Renderer)(nil) 40 ) 41 42 // Name returns the external tool name 43 func (p *Renderer) Name() string { 44 return p.MarkupName 45 } 46 47 // NeedPostProcess implements markup.Renderer 48 func (p *Renderer) NeedPostProcess() bool { 49 return p.MarkupRenderer.NeedPostProcess 50 } 51 52 // Extensions returns the supported extensions of the tool 53 func (p *Renderer) Extensions() []string { 54 return p.FileExtensions 55 } 56 57 // SanitizerRules implements markup.Renderer 58 func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule { 59 return p.MarkupSanitizerRules 60 } 61 62 // SanitizerDisabled disabled sanitize if return true 63 func (p *Renderer) SanitizerDisabled() bool { 64 return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe 65 } 66 67 // DisplayInIFrame represents whether render the content with an iframe 68 func (p *Renderer) DisplayInIFrame() bool { 69 return p.RenderContentMode == setting.RenderContentModeIframe 70 } 71 72 func envMark(envName string) string { 73 if runtime.GOOS == "windows" { 74 return "%" + envName + "%" 75 } 76 return "$" + envName 77 } 78 79 // Render renders the data of the document to HTML via the external tool. 80 func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { 81 var ( 82 command = strings.NewReplacer( 83 envMark("GITEA_PREFIX_SRC"), ctx.Links.SrcLink(), 84 envMark("GITEA_PREFIX_RAW"), ctx.Links.RawLink(), 85 ).Replace(p.Command) 86 commands = strings.Fields(command) 87 args = commands[1:] 88 ) 89 90 if p.IsInputFile { 91 // write to temp file 92 f, err := os.CreateTemp("", "gitea_input") 93 if err != nil { 94 return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) 95 } 96 tmpPath := f.Name() 97 defer func() { 98 if err := util.Remove(tmpPath); err != nil { 99 log.Warn("Unable to remove temporary file: %s: Error: %v", tmpPath, err) 100 } 101 }() 102 103 _, err = io.Copy(f, input) 104 if err != nil { 105 f.Close() 106 return fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err) 107 } 108 109 err = f.Close() 110 if err != nil { 111 return fmt.Errorf("%s close temp file when rendering %s failed: %w", p.Name(), p.Command, err) 112 } 113 args = append(args, f.Name()) 114 } 115 116 if ctx == nil || ctx.Ctx == nil { 117 if ctx == nil { 118 log.Warn("RenderContext not provided defaulting to empty ctx") 119 ctx = &markup.RenderContext{} 120 } 121 log.Warn("RenderContext did not provide context, defaulting to Shutdown context") 122 ctx.Ctx = graceful.GetManager().ShutdownContext() 123 } 124 125 processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.Links.SrcLink())) 126 defer finished() 127 128 cmd := exec.CommandContext(processCtx, commands[0], args...) 129 cmd.Env = append( 130 os.Environ(), 131 "GITEA_PREFIX_SRC="+ctx.Links.SrcLink(), 132 "GITEA_PREFIX_RAW="+ctx.Links.RawLink(), 133 ) 134 if !p.IsInputFile { 135 cmd.Stdin = input 136 } 137 var stderr bytes.Buffer 138 cmd.Stdout = output 139 cmd.Stderr = &stderr 140 process.SetSysProcAttribute(cmd) 141 142 if err := cmd.Run(); err != nil { 143 return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String()) 144 } 145 return nil 146 }