code.gitea.io/gitea@v1.19.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 urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1) 83 command = strings.NewReplacer(envMark("GITEA_PREFIX_SRC"), ctx.URLPrefix, 84 envMark("GITEA_PREFIX_RAW"), urlRawPrefix).Replace(p.Command) 85 commands = strings.Fields(command) 86 args = commands[1:] 87 ) 88 89 if p.IsInputFile { 90 // write to temp file 91 f, err := os.CreateTemp("", "gitea_input") 92 if err != nil { 93 return fmt.Errorf("%s create temp file when rendering %s failed: %w", p.Name(), p.Command, err) 94 } 95 tmpPath := f.Name() 96 defer func() { 97 if err := util.Remove(tmpPath); err != nil { 98 log.Warn("Unable to remove temporary file: %s: Error: %v", tmpPath, err) 99 } 100 }() 101 102 _, err = io.Copy(f, input) 103 if err != nil { 104 f.Close() 105 return fmt.Errorf("%s write data to temp file when rendering %s failed: %w", p.Name(), p.Command, err) 106 } 107 108 err = f.Close() 109 if err != nil { 110 return fmt.Errorf("%s close temp file when rendering %s failed: %w", p.Name(), p.Command, err) 111 } 112 args = append(args, f.Name()) 113 } 114 115 if ctx == nil || ctx.Ctx == nil { 116 if ctx == nil { 117 log.Warn("RenderContext not provided defaulting to empty ctx") 118 ctx = &markup.RenderContext{} 119 } 120 log.Warn("RenderContext did not provide context, defaulting to Shutdown context") 121 ctx.Ctx = graceful.GetManager().ShutdownContext() 122 } 123 124 processCtx, _, finished := process.GetManager().AddContext(ctx.Ctx, fmt.Sprintf("Render [%s] for %s", commands[0], ctx.URLPrefix)) 125 defer finished() 126 127 cmd := exec.CommandContext(processCtx, commands[0], args...) 128 cmd.Env = append( 129 os.Environ(), 130 "GITEA_PREFIX_SRC="+ctx.URLPrefix, 131 "GITEA_PREFIX_RAW="+urlRawPrefix, 132 ) 133 if !p.IsInputFile { 134 cmd.Stdin = input 135 } 136 var stderr bytes.Buffer 137 cmd.Stdout = output 138 cmd.Stderr = &stderr 139 process.SetSysProcAttribute(cmd) 140 141 if err := cmd.Run(); err != nil { 142 return fmt.Errorf("%s render run command %s %v failed: %w\nStderr: %s", p.Name(), commands[0], args, err, stderr.String()) 143 } 144 return nil 145 }