github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/utils/html.go (about)

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