| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 | // 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 (	"crypto/sha256"	"encoding/hex"	"html/template"	"io"	"io/ioutil"	"mime/multipart"	"net/http"	"net/url"	"os"	"path"	"path/filepath"	"reflect"	"strconv"	"strings"	"time"	"github.com/Unknwon/com"	"github.com/go-macaron/inject"	"golang.org/x/crypto/pbkdf2")// Locale reprents a localization interface.type Locale interface {	Language() string	Tr(string, ...interface{}) string}// RequestBody represents a request body.type RequestBody struct {	reader io.ReadCloser}// Bytes reads and returns content of request body in bytes.func (rb *RequestBody) Bytes() ([]byte, error) {	return ioutil.ReadAll(rb.reader)}// String reads and returns content of request body in string.func (rb *RequestBody) String() (string, error) {	data, err := rb.Bytes()	return string(data), err}// ReadCloser returns a ReadCloser for request body.func (rb *RequestBody) ReadCloser() io.ReadCloser {	return rb.reader}// Request represents an HTTP request received by a server or to be sent by a client.type Request struct {	*http.Request}func (r *Request) Body() *RequestBody {	return &RequestBody{r.Request.Body}}// ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context).type ContextInvoker func(ctx *Context)func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {	invoke(params[0].(*Context))	return nil, nil}// Context represents the runtime context of current request of Macaron instance.// It is the integration of most frequently used middlewares and helper methods.type Context struct {	inject.Injector	handlers []Handler	action   Handler	index    int	*Router	Req    Request	Resp   ResponseWriter	params Params	Render	Locale	Data map[string]interface{}}func (c *Context) handler() Handler {	if c.index < len(c.handlers) {		return c.handlers[c.index]	}	if c.index == len(c.handlers) {		return c.action	}	panic("invalid index for context handler")}func (c *Context) Next() {	c.index += 1	c.run()}func (c *Context) Written() bool {	return c.Resp.Written()}func (c *Context) run() {	for c.index <= len(c.handlers) {		vals, err := c.Invoke(c.handler())		if err != nil {			panic(err)		}		c.index += 1		// if the handler returned something, write it to the http response		if len(vals) > 0 {			ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil)))			handleReturn := ev.Interface().(ReturnHandler)			handleReturn(c, vals)		}		if c.Written() {			return		}	}}// RemoteAddr returns more real IP address.func (ctx *Context) RemoteAddr() string {	addr := ctx.Req.Header.Get("X-Real-IP")	if len(addr) == 0 {		addr = ctx.Req.Header.Get("X-Forwarded-For")		if addr == "" {			addr = ctx.Req.RemoteAddr			if i := strings.LastIndex(addr, ":"); i > -1 {				addr = addr[:i]			}		}	}	return addr}func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {	if len(data) <= 0 {		ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)	} else if len(data) == 1 {		ctx.Render.HTMLSet(status, setName, tplName, data[0])	} else {		ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))	}}// HTML calls Render.HTML but allows less arguments.func (ctx *Context) HTML(status int, name string, data ...interface{}) {	ctx.renderHTML(status, DEFAULT_TPL_SET_NAME, name, data...)}// HTML calls Render.HTMLSet but allows less arguments.func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) {	ctx.renderHTML(status, setName, tplName, data...)}func (ctx *Context) Redirect(location string, status ...int) {	code := http.StatusFound	if len(status) == 1 {		code = status[0]	}	http.Redirect(ctx.Resp, ctx.Req.Request, location, code)}// Maximum amount of memory to use when parsing a multipart form.// Set this to whatever value you prefer; default is 10 MB.var MaxMemory = int64(1024 * 1024 * 10)func (ctx *Context) parseForm() {	if ctx.Req.Form != nil {		return	}	contentType := ctx.Req.Header.Get(_CONTENT_TYPE)	if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&		len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {		ctx.Req.ParseMultipartForm(MaxMemory)	} else {		ctx.Req.ParseForm()	}}// Query querys form parameter.func (ctx *Context) Query(name string) string {	ctx.parseForm()	return ctx.Req.Form.Get(name)}// QueryTrim querys and trims spaces form parameter.func (ctx *Context) QueryTrim(name string) string {	return strings.TrimSpace(ctx.Query(name))}// QueryStrings returns a list of results by given query name.func (ctx *Context) QueryStrings(name string) []string {	ctx.parseForm()	vals, ok := ctx.Req.Form[name]	if !ok {		return []string{}	}	return vals}// QueryEscape returns escapred query result.func (ctx *Context) QueryEscape(name string) string {	return template.HTMLEscapeString(ctx.Query(name))}// QueryBool returns query result in bool type.func (ctx *Context) QueryBool(name string) bool {	v, _ := strconv.ParseBool(ctx.Query(name))	return v}// QueryInt returns query result in int type.func (ctx *Context) QueryInt(name string) int {	return com.StrTo(ctx.Query(name)).MustInt()}// QueryInt64 returns query result in int64 type.func (ctx *Context) QueryInt64(name string) int64 {	return com.StrTo(ctx.Query(name)).MustInt64()}// QueryFloat64 returns query result in float64 type.func (ctx *Context) QueryFloat64(name string) float64 {	v, _ := strconv.ParseFloat(ctx.Query(name), 64)	return v}// Params returns value of given param name.// e.g. ctx.Params(":uid") or ctx.Params("uid")func (ctx *Context) Params(name string) string {	if len(name) == 0 {		return ""	}	if len(name) > 1 && name[0] != ':' {		name = ":" + name	}	return ctx.params[name]}// SetParams sets value of param with given name.func (ctx *Context) SetParams(name, val string) {	if !strings.HasPrefix(name, ":") {		name = ":" + name	}	ctx.params[name] = val}// ReplaceAllParams replace all current params with given paramsfunc (ctx *Context) ReplaceAllParams(params Params) {	ctx.params = params;}// ParamsEscape returns escapred params result.// e.g. ctx.ParamsEscape(":uname")func (ctx *Context) ParamsEscape(name string) string {	return template.HTMLEscapeString(ctx.Params(name))}// ParamsInt returns params result in int type.// e.g. ctx.ParamsInt(":uid")func (ctx *Context) ParamsInt(name string) int {	return com.StrTo(ctx.Params(name)).MustInt()}// ParamsInt64 returns params result in int64 type.// e.g. ctx.ParamsInt64(":uid")func (ctx *Context) ParamsInt64(name string) int64 {	return com.StrTo(ctx.Params(name)).MustInt64()}// ParamsFloat64 returns params result in int64 type.// e.g. ctx.ParamsFloat64(":uid")func (ctx *Context) ParamsFloat64(name string) float64 {	v, _ := strconv.ParseFloat(ctx.Params(name), 64)	return v}// GetFile returns information about user upload file by given form field name.func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {	return ctx.Req.FormFile(name)}// SaveToFile reads a file from request by field name and saves to given path.func (ctx *Context) SaveToFile(name, savePath string) error {	fr, _, err := ctx.GetFile(name)	if err != nil {		return err	}	defer fr.Close()	fw, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)	if err != nil {		return err	}	defer fw.Close()	_, err = io.Copy(fw, fr)	return err}// SetCookie sets given cookie value to response header.// FIXME: IE support? http://golanghome.com/post/620#reply2func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {	cookie := http.Cookie{}	cookie.Name = name	cookie.Value = url.QueryEscape(value)	if len(others) > 0 {		switch v := others[0].(type) {		case int:			cookie.MaxAge = v		case int64:			cookie.MaxAge = int(v)		case int32:			cookie.MaxAge = int(v)		}	}	cookie.Path = "/"	if len(others) > 1 {		if v, ok := others[1].(string); ok && len(v) > 0 {			cookie.Path = v		}	}	if len(others) > 2 {		if v, ok := others[2].(string); ok && len(v) > 0 {			cookie.Domain = v		}	}	if len(others) > 3 {		switch v := others[3].(type) {		case bool:			cookie.Secure = v		default:			if others[3] != nil {				cookie.Secure = true			}		}	}	if len(others) > 4 {		if v, ok := others[4].(bool); ok && v {			cookie.HttpOnly = true		}	}	if len(others) > 5 {		if v, ok := others[5].(time.Time); ok {			cookie.Expires = v			cookie.RawExpires = v.Format(time.UnixDate)		}	}	ctx.Resp.Header().Add("Set-Cookie", cookie.String())}// GetCookie returns given cookie value from request header.func (ctx *Context) GetCookie(name string) string {	cookie, err := ctx.Req.Cookie(name)	if err != nil {		return ""	}	val, _ := url.QueryUnescape(cookie.Value)	return val}// GetCookieInt returns cookie result in int type.func (ctx *Context) GetCookieInt(name string) int {	return com.StrTo(ctx.GetCookie(name)).MustInt()}// GetCookieInt64 returns cookie result in int64 type.func (ctx *Context) GetCookieInt64(name string) int64 {	return com.StrTo(ctx.GetCookie(name)).MustInt64()}// GetCookieFloat64 returns cookie result in float64 type.func (ctx *Context) GetCookieFloat64(name string) float64 {	v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)	return v}var defaultCookieSecret string// SetDefaultCookieSecret sets global default secure cookie secret.func (m *Macaron) SetDefaultCookieSecret(secret string) {	defaultCookieSecret = secret}// SetSecureCookie sets given cookie value to response header with default secret string.func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) {	ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...)}// GetSecureCookie returns given cookie value from request header with default secret string.func (ctx *Context) GetSecureCookie(key string) (string, bool) {	return ctx.GetSuperSecureCookie(defaultCookieSecret, key)}// SetSuperSecureCookie sets given cookie value to response header with secret string.func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {	key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)	text, err := com.AESGCMEncrypt(key, []byte(value))	if err != nil {		panic("error encrypting cookie: " + err.Error())	}	ctx.SetCookie(name, hex.EncodeToString(text), others...)}// GetSuperSecureCookie returns given cookie value from request header with secret string.func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {	val := ctx.GetCookie(name)	if val == "" {		return "", false	}	text, err := hex.DecodeString(val)	if err != nil {		return "", false	}	key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)	text, err = com.AESGCMDecrypt(key, text)	return string(text), err == nil}func (ctx *Context) setRawContentHeader() {	ctx.Resp.Header().Set("Content-Description", "Raw content")	ctx.Resp.Header().Set("Content-Type", "text/plain")	ctx.Resp.Header().Set("Expires", "0")	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")	ctx.Resp.Header().Set("Pragma", "public")}// ServeContent serves given content to response.func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {	modtime := time.Now()	for _, p := range params {		switch v := p.(type) {		case time.Time:			modtime = v		}	}	ctx.setRawContentHeader()	http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)}// ServeFileContent serves given file as content to response.func (ctx *Context) ServeFileContent(file string, names ...string) {	var name string	if len(names) > 0 {		name = names[0]	} else {		name = path.Base(file)	}	f, err := os.Open(file)	if err != nil {		if Env == PROD {			http.Error(ctx.Resp, "Internal Server Error", 500)		} else {			http.Error(ctx.Resp, err.Error(), 500)		}		return	}	defer f.Close()	ctx.setRawContentHeader()	http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f)}// ServeFile serves given file to response.func (ctx *Context) ServeFile(file string, names ...string) {	var name string	if len(names) > 0 {		name = names[0]	} else {		name = path.Base(file)	}	ctx.Resp.Header().Set("Content-Description", "File Transfer")	ctx.Resp.Header().Set("Content-Type", "application/octet-stream")	ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)	ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")	ctx.Resp.Header().Set("Expires", "0")	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")	ctx.Resp.Header().Set("Pragma", "public")	http.ServeFile(ctx.Resp, ctx.Req.Request, file)}// ChangeStaticPath changes static path from old to new one.func (ctx *Context) ChangeStaticPath(oldPath, newPath string) {	if !filepath.IsAbs(oldPath) {		oldPath = filepath.Join(Root, oldPath)	}	dir := statics.Get(oldPath)	if dir != nil {		statics.Delete(oldPath)		if !filepath.IsAbs(newPath) {			newPath = filepath.Join(Root, newPath)		}		*dir = http.Dir(newPath)		statics.Set(dir)	}}
 |