github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/markup/external/external.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package external 7 8 import ( 9 "fmt" 10 "io" 11 "os" 12 "os/exec" 13 "runtime" 14 "strings" 15 16 "github.com/gitbundle/modules/graceful" 17 "github.com/gitbundle/modules/log" 18 "github.com/gitbundle/modules/markup" 19 "github.com/gitbundle/modules/process" 20 "github.com/gitbundle/modules/setting" 21 "github.com/gitbundle/modules/util" 22 ) 23 24 // RegisterRenderers registers all supported third part renderers according settings 25 func RegisterRenderers() { 26 for _, renderer := range setting.ExternalMarkupRenderers { 27 if renderer.Enabled && renderer.Command != "" && len(renderer.FileExtensions) > 0 { 28 markup.RegisterRenderer(&Renderer{renderer}) 29 } 30 } 31 } 32 33 // Renderer implements markup.Renderer for external tools 34 type Renderer struct { 35 *setting.MarkupRenderer 36 } 37 38 var ( 39 _ markup.PostProcessRenderer = (*Renderer)(nil) 40 _ markup.ExternalRenderer = (*Renderer)(nil) 41 ) 42 43 // Name returns the external tool name 44 func (p *Renderer) Name() string { 45 return p.MarkupName 46 } 47 48 // NeedPostProcess implements markup.Renderer 49 func (p *Renderer) NeedPostProcess() bool { 50 return p.MarkupRenderer.NeedPostProcess 51 } 52 53 // Extensions returns the supported extensions of the tool 54 func (p *Renderer) Extensions() []string { 55 return p.FileExtensions 56 } 57 58 // SanitizerRules implements markup.Renderer 59 func (p *Renderer) SanitizerRules() []setting.MarkupSanitizerRule { 60 return p.MarkupSanitizerRules 61 } 62 63 // SanitizerDisabled disabled sanitize if return true 64 func (p *Renderer) SanitizerDisabled() bool { 65 return p.RenderContentMode == setting.RenderContentModeNoSanitizer || p.RenderContentMode == setting.RenderContentModeIframe 66 } 67 68 // DisplayInIFrame represents whether render the content with an iframe 69 func (p *Renderer) DisplayInIFrame() bool { 70 return p.RenderContentMode == setting.RenderContentModeIframe 71 } 72 73 func envMark(envName string) string { 74 if runtime.GOOS == "windows" { 75 return "%" + envName + "%" 76 } 77 return "$" + envName 78 } 79 80 // Render renders the data of the document to HTML via the external tool. 81 func (p *Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { 82 var ( 83 urlRawPrefix = strings.Replace(ctx.URLPrefix, "/src/", "/raw/", 1) 84 command = strings.NewReplacer(envMark("GITBUNDLE_PREFIX_SRC"), ctx.URLPrefix, 85 envMark("GITBUNDLE_PREFIX_RAW"), urlRawPrefix).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("", "gitbundle_input") 93 if err != nil { 94 return fmt.Errorf("%s create temp file when rendering %s failed: %v", 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: %v", 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: %v", 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.URLPrefix)) 126 defer finished() 127 128 cmd := exec.CommandContext(processCtx, commands[0], args...) 129 cmd.Env = append( 130 os.Environ(), 131 "GITBUNDLE_PREFIX_SRC="+ctx.URLPrefix, 132 "GITBUNDLE_PREFIX_RAW="+urlRawPrefix, 133 ) 134 if !p.IsInputFile { 135 cmd.Stdin = input 136 } 137 cmd.Stdout = output 138 process.SetSysProcAttribute(cmd) 139 140 if err := cmd.Run(); err != nil { 141 return fmt.Errorf("%s render run command %s %v failed: %v", p.Name(), commands[0], args, err) 142 } 143 return nil 144 }