github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/utils/html.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package utils 5 6 import ( 7 "bytes" 8 "errors" 9 "html/template" 10 "io" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "sync/atomic" 15 16 "github.com/fsnotify/fsnotify" 17 "github.com/mattermost/go-i18n/i18n" 18 19 "github.com/mattermost/mattermost-server/v5/mlog" 20 "github.com/mattermost/mattermost-server/v5/utils/fileutils" 21 ) 22 23 type HTMLTemplateWatcher struct { 24 templates atomic.Value 25 stop chan struct{} 26 stopped chan struct{} 27 } 28 29 func NewHTMLTemplateWatcher(directory string) (*HTMLTemplateWatcher, error) { 30 templatesDir, _ := fileutils.FindDir(directory) 31 mlog.Debug("Parsing server templates", mlog.String("templates_directory", templatesDir)) 32 33 ret := &HTMLTemplateWatcher{ 34 stop: make(chan struct{}), 35 stopped: make(chan struct{}), 36 } 37 38 watcher, err := fsnotify.NewWatcher() 39 if err != nil { 40 return nil, err 41 } 42 43 if err = watcher.Add(templatesDir); err != nil { 44 return nil, err 45 } 46 47 htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html")) 48 if err != nil { 49 return nil, err 50 } 51 ret.templates.Store(htmlTemplates) 52 53 go func() { 54 defer close(ret.stopped) 55 defer watcher.Close() 56 57 for { 58 select { 59 case <-ret.stop: 60 return 61 case event := <-watcher.Events: 62 if event.Op&fsnotify.Write == fsnotify.Write { 63 mlog.Info("Re-parsing templates because of modified file", mlog.String("file_name", event.Name)) 64 if htmlTemplates, err := template.ParseGlob(filepath.Join(templatesDir, "*.html")); err != nil { 65 mlog.Error("Failed to parse templates.", mlog.Err(err)) 66 } else { 67 ret.templates.Store(htmlTemplates) 68 } 69 } 70 case err := <-watcher.Errors: 71 mlog.Error("Failed in directory watcher", mlog.Err(err)) 72 } 73 } 74 }() 75 76 return ret, nil 77 } 78 79 func (w *HTMLTemplateWatcher) Templates() *template.Template { 80 return w.templates.Load().(*template.Template) 81 } 82 83 func (w *HTMLTemplateWatcher) Close() { 84 close(w.stop) 85 <-w.stopped 86 } 87 88 type HTMLTemplate struct { 89 Templates *template.Template 90 TemplateName string 91 Props map[string]interface{} 92 Html map[string]template.HTML 93 } 94 95 func NewHTMLTemplate(templates *template.Template, templateName string) *HTMLTemplate { 96 return &HTMLTemplate{ 97 Templates: templates, 98 TemplateName: templateName, 99 Props: make(map[string]interface{}), 100 Html: make(map[string]template.HTML), 101 } 102 } 103 104 func (t *HTMLTemplate) Render() string { 105 var text bytes.Buffer 106 t.RenderToWriter(&text) 107 return text.String() 108 } 109 110 func (t *HTMLTemplate) RenderToWriter(w io.Writer) error { 111 if t.Templates == nil { 112 return errors.New("no html templates") 113 } 114 115 if err := t.Templates.ExecuteTemplate(w, t.TemplateName, t); err != nil { 116 mlog.Warn("Error rendering template", mlog.String("template_name", t.TemplateName), mlog.Err(err)) 117 return err 118 } 119 120 return nil 121 } 122 123 func TranslateAsHtml(t i18n.TranslateFunc, translationID string, args map[string]interface{}) template.HTML { 124 message := t(translationID, escapeForHtml(args)) 125 message = strings.Replace(message, "[[", "<strong>", -1) 126 message = strings.Replace(message, "]]", "</strong>", -1) 127 return template.HTML(message) 128 } 129 130 func escapeForHtml(arg interface{}) interface{} { 131 switch typedArg := arg.(type) { 132 case string: 133 return template.HTMLEscapeString(typedArg) 134 case *string: 135 return template.HTMLEscapeString(*typedArg) 136 case map[string]interface{}: 137 safeArg := make(map[string]interface{}, len(typedArg)) 138 for key, value := range typedArg { 139 safeArg[key] = escapeForHtml(value) 140 } 141 return safeArg 142 default: 143 mlog.Warn( 144 "Unable to escape value for HTML template", 145 mlog.Any("html_template", arg), 146 mlog.String("template_type", reflect.ValueOf(arg).Type().String()), 147 ) 148 return "" 149 } 150 }