| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725 | // Copyright 2013 Martini Authors// Copyright 2014 The Macaron Authors//// Licensed under the Apache License, Version 2.0 (the "License"): you may// not use this file except in compliance with the License. You may obtain// a copy of the License at////     http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the// License for the specific language governing permissions and limitations// under the License.package macaronimport (	"bytes"	"encoding/json"	"encoding/xml"	"fmt"	"html/template"	"io"	"io/ioutil"	"net/http"	"os"	"path"	"path/filepath"	"strings"	"sync"	"time"	"github.com/Unknwon/com")const (	_CONTENT_TYPE    = "Content-Type"	_CONTENT_LENGTH  = "Content-Length"	_CONTENT_BINARY  = "application/octet-stream"	_CONTENT_JSON    = "application/json"	_CONTENT_HTML    = "text/html"	_CONTENT_PLAIN   = "text/plain"	_CONTENT_XHTML   = "application/xhtml+xml"	_CONTENT_XML     = "text/xml"	_DEFAULT_CHARSET = "UTF-8")var (	// Provides a temporary buffer to execute templates into and catch errors.	bufpool = sync.Pool{		New: func() interface{} { return new(bytes.Buffer) },	}	// Included helper functions for use when rendering html	helperFuncs = template.FuncMap{		"yield": func() (string, error) {			return "", fmt.Errorf("yield called with no layout defined")		},		"current": func() (string, error) {			return "", nil		},	})type (	// TemplateFile represents a interface of template file that has name and can be read.	TemplateFile interface {		Name() string		Data() []byte		Ext() string	}	// TemplateFileSystem represents a interface of template file system that able to list all files.	TemplateFileSystem interface {		ListFiles() []TemplateFile		Get(string) (io.Reader, error)	}	// Delims represents a set of Left and Right delimiters for HTML template rendering	Delims struct {		// Left delimiter, defaults to {{		Left string		// Right delimiter, defaults to }}		Right string	}	// RenderOptions represents a struct for specifying configuration options for the Render middleware.	RenderOptions struct {		// Directory to load templates. Default is "templates".		Directory string		// Addtional directories to overwite templates.		AppendDirectories []string		// Layout template name. Will not render a layout if "". Default is to "".		Layout string		// Extensions to parse template files from. Defaults are [".tmpl", ".html"].		Extensions []string		// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].		Funcs []template.FuncMap		// Delims sets the action delimiters to the specified strings in the Delims struct.		Delims Delims		// Appends the given charset to the Content-Type header. Default is "UTF-8".		Charset string		// Outputs human readable JSON.		IndentJSON bool		// Outputs human readable XML.		IndentXML bool		// Prefixes the JSON output with the given bytes.		PrefixJSON []byte		// Prefixes the XML output with the given bytes.		PrefixXML []byte		// Allows changing of output to XHTML instead of HTML. Default is "text/html"		HTMLContentType string		// TemplateFileSystem is the interface for supporting any implmentation of template file system.		TemplateFileSystem	}	// HTMLOptions is a struct for overriding some rendering Options for specific HTML call	HTMLOptions struct {		// Layout template name. Overrides Options.Layout.		Layout string	}	Render interface {		http.ResponseWriter		SetResponseWriter(http.ResponseWriter)		JSON(int, interface{})		JSONString(interface{}) (string, error)		RawData(int, []byte)   // Serve content as binary		PlainText(int, []byte) // Serve content as plain text		HTML(int, string, interface{}, ...HTMLOptions)		HTMLSet(int, string, string, interface{}, ...HTMLOptions)		HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)		HTMLString(string, interface{}, ...HTMLOptions) (string, error)		HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)		HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)		XML(int, interface{})		Error(int, ...string)		Status(int)		SetTemplatePath(string, string)		HasTemplateSet(string) bool	})// TplFile implements TemplateFile interface.type TplFile struct {	name string	data []byte	ext  string}// NewTplFile cerates new template file with given name and data.func NewTplFile(name string, data []byte, ext string) *TplFile {	return &TplFile{name, data, ext}}func (f *TplFile) Name() string {	return f.name}func (f *TplFile) Data() []byte {	return f.data}func (f *TplFile) Ext() string {	return f.ext}// TplFileSystem implements TemplateFileSystem interface.type TplFileSystem struct {	files []TemplateFile}// NewTemplateFileSystem creates new template file system with given options.func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {	fs := TplFileSystem{}	fs.files = make([]TemplateFile, 0, 10)	// Directories are composed in reverse order because later one overwrites previous ones,	// so once found, we can directly jump out of the loop.	dirs := make([]string, 0, len(opt.AppendDirectories)+1)	for i := len(opt.AppendDirectories) - 1; i >= 0; i-- {		dirs = append(dirs, opt.AppendDirectories[i])	}	dirs = append(dirs, opt.Directory)	var err error	for i := range dirs {		// Skip ones that does not exists for symlink test,		// but allow non-symlink ones added after start.		if !com.IsExist(dirs[i]) {			continue		}		dirs[i], err = filepath.EvalSymlinks(dirs[i])		if err != nil {			panic("EvalSymlinks(" + dirs[i] + "): " + err.Error())		}	}	lastDir := dirs[len(dirs)-1]	// We still walk the last (original) directory because it's non-sense we load templates not exist in original directory.	if err = filepath.Walk(lastDir, func(path string, info os.FileInfo, err error) error {		r, err := filepath.Rel(lastDir, path)		if err != nil {			return err		}		ext := GetExt(r)		for _, extension := range opt.Extensions {			if ext != extension {				continue			}			var data []byte			if !omitData {				// Loop over candidates of directory, break out once found.				// The file always exists because it's inside the walk function,				// and read original file is the worst case.				for i := range dirs {					path = filepath.Join(dirs[i], r)					if !com.IsFile(path) {						continue					}					data, err = ioutil.ReadFile(path)					if err != nil {						return err					}					break				}			}			name := filepath.ToSlash((r[0 : len(r)-len(ext)]))			fs.files = append(fs.files, NewTplFile(name, data, ext))		}		return nil	}); err != nil {		panic("NewTemplateFileSystem: " + err.Error())	}	return fs}func (fs TplFileSystem) ListFiles() []TemplateFile {	return fs.files}func (fs TplFileSystem) Get(name string) (io.Reader, error) {	for i := range fs.files {		if fs.files[i].Name()+fs.files[i].Ext() == name {			return bytes.NewReader(fs.files[i].Data()), nil		}	}	return nil, fmt.Errorf("file '%s' not found", name)}func PrepareCharset(charset string) string {	if len(charset) != 0 {		return "; charset=" + charset	}	return "; charset=" + _DEFAULT_CHARSET}func GetExt(s string) string {	index := strings.Index(s, ".")	if index == -1 {		return ""	}	return s[index:]}func compile(opt RenderOptions) *template.Template {	t := template.New(opt.Directory)	t.Delims(opt.Delims.Left, opt.Delims.Right)	// Parse an initial template in case we don't have any.	template.Must(t.Parse("Macaron"))	if opt.TemplateFileSystem == nil {		opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)	}	for _, f := range opt.TemplateFileSystem.ListFiles() {		tmpl := t.New(f.Name())		for _, funcs := range opt.Funcs {			tmpl.Funcs(funcs)		}		// Bomb out if parse fails. We don't want any silent server starts.		template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))	}	return t}const (	DEFAULT_TPL_SET_NAME = "DEFAULT")// TemplateSet represents a template set of type *template.Template.type TemplateSet struct {	lock sync.RWMutex	sets map[string]*template.Template	dirs map[string]string}// NewTemplateSet initializes a new empty template set.func NewTemplateSet() *TemplateSet {	return &TemplateSet{		sets: make(map[string]*template.Template),		dirs: make(map[string]string),	}}func (ts *TemplateSet) Set(name string, opt *RenderOptions) *template.Template {	t := compile(*opt)	ts.lock.Lock()	defer ts.lock.Unlock()	ts.sets[name] = t	ts.dirs[name] = opt.Directory	return t}func (ts *TemplateSet) Get(name string) *template.Template {	ts.lock.RLock()	defer ts.lock.RUnlock()	return ts.sets[name]}func (ts *TemplateSet) GetDir(name string) string {	ts.lock.RLock()	defer ts.lock.RUnlock()	return ts.dirs[name]}func prepareRenderOptions(options []RenderOptions) RenderOptions {	var opt RenderOptions	if len(options) > 0 {		opt = options[0]	}	// Defaults.	if len(opt.Directory) == 0 {		opt.Directory = "templates"	}	if len(opt.Extensions) == 0 {		opt.Extensions = []string{".tmpl", ".html"}	}	if len(opt.HTMLContentType) == 0 {		opt.HTMLContentType = _CONTENT_HTML	}	return opt}func ParseTplSet(tplSet string) (tplName string, tplDir string) {	tplSet = strings.TrimSpace(tplSet)	if len(tplSet) == 0 {		panic("empty template set argument")	}	infos := strings.Split(tplSet, ":")	if len(infos) == 1 {		tplDir = infos[0]		tplName = path.Base(tplDir)	} else {		tplName = infos[0]		tplDir = infos[1]	}	if !com.IsDir(tplDir) {		panic("template set path does not exist or is not a directory")	}	return tplName, tplDir}func renderHandler(opt RenderOptions, tplSets []string) Handler {	cs := PrepareCharset(opt.Charset)	ts := NewTemplateSet()	ts.Set(DEFAULT_TPL_SET_NAME, &opt)	var tmpOpt RenderOptions	for _, tplSet := range tplSets {		tplName, tplDir := ParseTplSet(tplSet)		tmpOpt = opt		tmpOpt.Directory = tplDir		ts.Set(tplName, &tmpOpt)	}	return func(ctx *Context) {		r := &TplRender{			ResponseWriter:  ctx.Resp,			TemplateSet:     ts,			Opt:             &opt,			CompiledCharset: cs,		}		ctx.Data["TmplLoadTimes"] = func() string {			if r.startTime.IsZero() {				return ""			}			return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"		}		ctx.Render = r		ctx.MapTo(r, (*Render)(nil))	}}// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.// An single variadic macaron.RenderOptions struct can be optionally provided to configure// HTML rendering. The default directory for templates is "templates" and the default// file extension is ".tmpl" and ".html".//// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the// MACARON_ENV environment variable to "production".func Renderer(options ...RenderOptions) Handler {	return renderHandler(prepareRenderOptions(options), []string{})}func Renderers(options RenderOptions, tplSets ...string) Handler {	return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets)}type TplRender struct {	http.ResponseWriter	*TemplateSet	Opt             *RenderOptions	CompiledCharset string	startTime time.Time}func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) {	r.ResponseWriter = rw}func (r *TplRender) JSON(status int, v interface{}) {	var (		result []byte		err    error	)	if r.Opt.IndentJSON {		result, err = json.MarshalIndent(v, "", "  ")	} else {		result, err = json.Marshal(v)	}	if err != nil {		http.Error(r, err.Error(), 500)		return	}	// json rendered fine, write out the result	r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset)	r.WriteHeader(status)	if len(r.Opt.PrefixJSON) > 0 {		r.Write(r.Opt.PrefixJSON)	}	r.Write(result)}func (r *TplRender) JSONString(v interface{}) (string, error) {	var result []byte	var err error	if r.Opt.IndentJSON {		result, err = json.MarshalIndent(v, "", "  ")	} else {		result, err = json.Marshal(v)	}	if err != nil {		return "", err	}	return string(result), nil}func (r *TplRender) XML(status int, v interface{}) {	var result []byte	var err error	if r.Opt.IndentXML {		result, err = xml.MarshalIndent(v, "", "  ")	} else {		result, err = xml.Marshal(v)	}	if err != nil {		http.Error(r, err.Error(), 500)		return	}	// XML rendered fine, write out the result	r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset)	r.WriteHeader(status)	if len(r.Opt.PrefixXML) > 0 {		r.Write(r.Opt.PrefixXML)	}	r.Write(result)}func (r *TplRender) data(status int, contentType string, v []byte) {	if r.Header().Get(_CONTENT_TYPE) == "" {		r.Header().Set(_CONTENT_TYPE, contentType)	}	r.WriteHeader(status)	r.Write(v)}func (r *TplRender) RawData(status int, v []byte) {	r.data(status, _CONTENT_BINARY, v)}func (r *TplRender) PlainText(status int, v []byte) {	r.data(status, _CONTENT_PLAIN, v)}func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {	buf := bufpool.Get().(*bytes.Buffer)	return buf, t.ExecuteTemplate(buf, name, data)}func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {	funcs := template.FuncMap{		"yield": func() (template.HTML, error) {			buf, err := r.execute(t, tplName, data)			// return safe html here since we are rendering our own template			return template.HTML(buf.String()), err		},		"current": func() (string, error) {			return tplName, nil		},	}	t.Funcs(funcs)}func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {	t := r.TemplateSet.Get(setName)	if Env == DEV {		opt := *r.Opt		opt.Directory = r.TemplateSet.GetDir(setName)		t = r.TemplateSet.Set(setName, &opt)	}	if t == nil {		return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)	}	opt := r.prepareHTMLOptions(htmlOpt)	if len(opt.Layout) > 0 {		r.addYield(t, tplName, data)		tplName = opt.Layout	}	out, err := r.execute(t, tplName, data)	if err != nil {		return nil, err	}	return out, nil}func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {	r.startTime = time.Now()	out, err := r.renderBytes(setName, tplName, data, htmlOpt...)	if err != nil {		http.Error(r, err.Error(), http.StatusInternalServerError)		return	}	r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset)	r.WriteHeader(status)	if _, err := out.WriteTo(r); err != nil {		out.Reset()	}	bufpool.Put(out)}func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {	r.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)}func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {	r.renderHTML(status, setName, tplName, data, htmlOpt...)}func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {	out, err := r.renderBytes(setName, tplName, data, htmlOpt...)	if err != nil {		return []byte(""), err	}	return out.Bytes(), nil}func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {	return r.HTMLSetBytes(DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)}func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {	p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)	return string(p), err}func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {	p, err := r.HTMLBytes(name, data, htmlOpt...)	return string(p), err}// Error writes the given HTTP status to the current ResponseWriterfunc (r *TplRender) Error(status int, message ...string) {	r.WriteHeader(status)	if len(message) > 0 {		r.Write([]byte(message[0]))	}}func (r *TplRender) Status(status int) {	r.WriteHeader(status)}func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {	if len(htmlOpt) > 0 {		return htmlOpt[0]	}	return HTMLOptions{		Layout: r.Opt.Layout,	}}func (r *TplRender) SetTemplatePath(setName, dir string) {	if len(setName) == 0 {		setName = DEFAULT_TPL_SET_NAME	}	opt := *r.Opt	opt.Directory = dir	r.TemplateSet.Set(setName, &opt)}func (r *TplRender) HasTemplateSet(name string) bool {	return r.TemplateSet.Get(name) != nil}// DummyRender is used when user does not choose any real render to use.// This way, we can print out friendly message which asks them to register one,// instead of ugly and confusing 'nil pointer' panic.type DummyRender struct {	http.ResponseWriter}func renderNotRegistered() {	panic("middleware render hasn't been registered")}func (r *DummyRender) SetResponseWriter(http.ResponseWriter) {	renderNotRegistered()}func (r *DummyRender) JSON(int, interface{}) {	renderNotRegistered()}func (r *DummyRender) JSONString(interface{}) (string, error) {	renderNotRegistered()	return "", nil}func (r *DummyRender) RawData(int, []byte) {	renderNotRegistered()}func (r *DummyRender) PlainText(int, []byte) {	renderNotRegistered()}func (r *DummyRender) HTML(int, string, interface{}, ...HTMLOptions) {	renderNotRegistered()}func (r *DummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) {	renderNotRegistered()}func (r *DummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) {	renderNotRegistered()	return "", nil}func (r *DummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) {	renderNotRegistered()	return "", nil}func (r *DummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) {	renderNotRegistered()	return nil, nil}func (r *DummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) {	renderNotRegistered()	return nil, nil}func (r *DummyRender) XML(int, interface{}) {	renderNotRegistered()}func (r *DummyRender) Error(int, ...string) {	renderNotRegistered()}func (r *DummyRender) Status(int) {	renderNotRegistered()}func (r *DummyRender) SetTemplatePath(string, string) {	renderNotRegistered()}func (r *DummyRender) HasTemplateSet(string) bool {	renderNotRegistered()	return false}
 |