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