1// Copyright 2014 The Macaron Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License"): you may
4// not use this file except in compliance with the License. You may obtain
5// a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12// License for the specific language governing permissions and limitations
13// under the License.
14
15package macaron
16
17import (
18	"encoding/json"
19	"html/template"
20	"net/http"
21	"net/url"
22	"reflect"
23	"strconv"
24	"strings"
25)
26
27// ContextInvoker is an inject.FastInvoker wrapper of func(ctx *Context).
28type ContextInvoker func(ctx *Context)
29
30// Invoke implements inject.FastInvoker which simplifies calls of `func(ctx *Context)` function.
31func (invoke ContextInvoker) Invoke(params []interface{}) ([]reflect.Value, error) {
32	invoke(params[0].(*Context))
33	return nil, nil
34}
35
36// Context represents the runtime context of current request of Macaron instance.
37// It is the integration of most frequently used middlewares and helper methods.
38type Context struct {
39	Injector
40	handlers []Handler
41	index    int
42
43	*Router
44	Req      *http.Request
45	Resp     ResponseWriter
46	template *template.Template
47}
48
49func (ctx *Context) handler() Handler {
50	if ctx.index < len(ctx.handlers) {
51		return ctx.handlers[ctx.index]
52	}
53	if ctx.index == len(ctx.handlers) {
54		return func() {}
55	}
56	panic("invalid index for context handler")
57}
58
59// Next runs the next handler in the context chain
60func (ctx *Context) Next() {
61	ctx.index++
62	ctx.run()
63}
64
65func (ctx *Context) run() {
66	for ctx.index <= len(ctx.handlers) {
67		if _, err := ctx.Invoke(ctx.handler()); err != nil {
68			panic(err)
69		}
70		ctx.index++
71		if ctx.Resp.Written() {
72			return
73		}
74	}
75}
76
77// RemoteAddr returns more real IP address.
78func (ctx *Context) RemoteAddr() string {
79	addr := ctx.Req.Header.Get("X-Real-IP")
80
81	if len(addr) == 0 {
82		// X-Forwarded-For may contain multiple IP addresses, separated by
83		// commas.
84		addr = strings.TrimSpace(strings.Split(ctx.Req.Header.Get("X-Forwarded-For"), ",")[0])
85	}
86
87	if len(addr) == 0 {
88		addr = ctx.Req.RemoteAddr
89		if i := strings.LastIndex(addr, ":"); i > -1 {
90			addr = addr[:i]
91		}
92	}
93	return addr
94}
95
96const (
97	headerContentType = "Content-Type"
98	contentTypeJSON   = "application/json; charset=UTF-8"
99	contentTypeHTML   = "text/html; charset=UTF-8"
100)
101
102// HTML renders the HTML with default template set.
103func (ctx *Context) HTML(status int, name string, data interface{}) {
104	ctx.Resp.Header().Set(headerContentType, contentTypeHTML)
105	ctx.Resp.WriteHeader(status)
106	if err := ctx.template.ExecuteTemplate(ctx.Resp, name, data); err != nil {
107		panic("Context.HTML:" + err.Error())
108	}
109}
110
111func (ctx *Context) JSON(status int, data interface{}) {
112	ctx.Resp.Header().Set(headerContentType, contentTypeJSON)
113	ctx.Resp.WriteHeader(status)
114	enc := json.NewEncoder(ctx.Resp)
115	if Env != PROD {
116		enc.SetIndent("", "  ")
117	}
118	if err := enc.Encode(data); err != nil {
119		panic("Context.JSON: " + err.Error())
120	}
121}
122
123// Redirect sends a redirect response
124func (ctx *Context) Redirect(location string, status ...int) {
125	code := http.StatusFound
126	if len(status) == 1 {
127		code = status[0]
128	}
129
130	http.Redirect(ctx.Resp, ctx.Req, location, code)
131}
132
133// MaxMemory is the maximum amount of memory to use when parsing a multipart form.
134// Set this to whatever value you prefer; default is 10 MB.
135var MaxMemory = int64(1024 * 1024 * 10)
136
137func (ctx *Context) parseForm() {
138	if ctx.Req.Form != nil {
139		return
140	}
141
142	contentType := ctx.Req.Header.Get(headerContentType)
143	if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") &&
144		len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") {
145		_ = ctx.Req.ParseMultipartForm(MaxMemory)
146	} else {
147		_ = ctx.Req.ParseForm()
148	}
149}
150
151// Query querys form parameter.
152func (ctx *Context) Query(name string) string {
153	ctx.parseForm()
154	return ctx.Req.Form.Get(name)
155}
156
157// QueryStrings returns a list of results by given query name.
158func (ctx *Context) QueryStrings(name string) []string {
159	ctx.parseForm()
160
161	vals, ok := ctx.Req.Form[name]
162	if !ok {
163		return []string{}
164	}
165	return vals
166}
167
168// QueryBool returns query result in bool type.
169func (ctx *Context) QueryBool(name string) bool {
170	v, _ := strconv.ParseBool(ctx.Query(name))
171	return v
172}
173
174// QueryInt returns query result in int type.
175func (ctx *Context) QueryInt(name string) int {
176	n, _ := strconv.Atoi(ctx.Query(name))
177	return n
178}
179
180// QueryInt64 returns query result in int64 type.
181func (ctx *Context) QueryInt64(name string) int64 {
182	n, _ := strconv.ParseInt(ctx.Query(name), 10, 64)
183	return n
184}
185
186// ParamsInt64 returns params result in int64 type.
187// e.g. ctx.ParamsInt64(":uid")
188func (ctx *Context) ParamsInt64(name string) int64 {
189	n, _ := strconv.ParseInt(Params(ctx.Req)[name], 10, 64)
190	return n
191}
192
193// GetCookie returns given cookie value from request header.
194func (ctx *Context) GetCookie(name string) string {
195	cookie, err := ctx.Req.Cookie(name)
196	if err != nil {
197		return ""
198	}
199	val, _ := url.QueryUnescape(cookie.Value)
200	return val
201}
202