1package echo
2
3import (
4	"bytes"
5	"encoding/json"
6	"encoding/xml"
7	"fmt"
8	"io"
9	"mime/multipart"
10	"net"
11	"net/http"
12	"net/url"
13	"os"
14	"path/filepath"
15	"strings"
16)
17
18type (
19	// Context represents the context of the current HTTP request. It holds request and
20	// response objects, path, path parameters, data and registered handler.
21	Context interface {
22		// Request returns `*http.Request`.
23		Request() *http.Request
24
25		// SetRequest sets `*http.Request`.
26		SetRequest(r *http.Request)
27
28		// Response returns `*Response`.
29		Response() *Response
30
31		// IsTLS returns true if HTTP connection is TLS otherwise false.
32		IsTLS() bool
33
34		// IsWebSocket returns true if HTTP connection is WebSocket otherwise false.
35		IsWebSocket() bool
36
37		// Scheme returns the HTTP protocol scheme, `http` or `https`.
38		Scheme() string
39
40		// RealIP returns the client's network address based on `X-Forwarded-For`
41		// or `X-Real-IP` request header.
42		RealIP() string
43
44		// Path returns the registered path for the handler.
45		Path() string
46
47		// SetPath sets the registered path for the handler.
48		SetPath(p string)
49
50		// Param returns path parameter by name.
51		Param(name string) string
52
53		// ParamNames returns path parameter names.
54		ParamNames() []string
55
56		// SetParamNames sets path parameter names.
57		SetParamNames(names ...string)
58
59		// ParamValues returns path parameter values.
60		ParamValues() []string
61
62		// SetParamValues sets path parameter values.
63		SetParamValues(values ...string)
64
65		// QueryParam returns the query param for the provided name.
66		QueryParam(name string) string
67
68		// QueryParams returns the query parameters as `url.Values`.
69		QueryParams() url.Values
70
71		// QueryString returns the URL query string.
72		QueryString() string
73
74		// FormValue returns the form field value for the provided name.
75		FormValue(name string) string
76
77		// FormParams returns the form parameters as `url.Values`.
78		FormParams() (url.Values, error)
79
80		// FormFile returns the multipart form file for the provided name.
81		FormFile(name string) (*multipart.FileHeader, error)
82
83		// MultipartForm returns the multipart form.
84		MultipartForm() (*multipart.Form, error)
85
86		// Cookie returns the named cookie provided in the request.
87		Cookie(name string) (*http.Cookie, error)
88
89		// SetCookie adds a `Set-Cookie` header in HTTP response.
90		SetCookie(cookie *http.Cookie)
91
92		// Cookies returns the HTTP cookies sent with the request.
93		Cookies() []*http.Cookie
94
95		// Get retrieves data from the context.
96		Get(key string) interface{}
97
98		// Set saves data in the context.
99		Set(key string, val interface{})
100
101		// Bind binds the request body into provided type `i`. The default binder
102		// does it based on Content-Type header.
103		Bind(i interface{}) error
104
105		// Validate validates provided `i`. It is usually called after `Context#Bind()`.
106		// Validator must be registered using `Echo#Validator`.
107		Validate(i interface{}) error
108
109		// Render renders a template with data and sends a text/html response with status
110		// code. Renderer must be registered using `Echo.Renderer`.
111		Render(code int, name string, data interface{}) error
112
113		// HTML sends an HTTP response with status code.
114		HTML(code int, html string) error
115
116		// HTMLBlob sends an HTTP blob response with status code.
117		HTMLBlob(code int, b []byte) error
118
119		// String sends a string response with status code.
120		String(code int, s string) error
121
122		// JSON sends a JSON response with status code.
123		JSON(code int, i interface{}) error
124
125		// JSONPretty sends a pretty-print JSON with status code.
126		JSONPretty(code int, i interface{}, indent string) error
127
128		// JSONBlob sends a JSON blob response with status code.
129		JSONBlob(code int, b []byte) error
130
131		// JSONP sends a JSONP response with status code. It uses `callback` to construct
132		// the JSONP payload.
133		JSONP(code int, callback string, i interface{}) error
134
135		// JSONPBlob sends a JSONP blob response with status code. It uses `callback`
136		// to construct the JSONP payload.
137		JSONPBlob(code int, callback string, b []byte) error
138
139		// XML sends an XML response with status code.
140		XML(code int, i interface{}) error
141
142		// XMLPretty sends a pretty-print XML with status code.
143		XMLPretty(code int, i interface{}, indent string) error
144
145		// XMLBlob sends an XML blob response with status code.
146		XMLBlob(code int, b []byte) error
147
148		// Blob sends a blob response with status code and content type.
149		Blob(code int, contentType string, b []byte) error
150
151		// Stream sends a streaming response with status code and content type.
152		Stream(code int, contentType string, r io.Reader) error
153
154		// File sends a response with the content of the file.
155		File(file string) error
156
157		// Attachment sends a response as attachment, prompting client to save the
158		// file.
159		Attachment(file string, name string) error
160
161		// Inline sends a response as inline, opening the file in the browser.
162		Inline(file string, name string) error
163
164		// NoContent sends a response with no body and a status code.
165		NoContent(code int) error
166
167		// Redirect redirects the request to a provided URL with status code.
168		Redirect(code int, url string) error
169
170		// Error invokes the registered HTTP error handler. Generally used by middleware.
171		Error(err error)
172
173		// Handler returns the matched handler by router.
174		Handler() HandlerFunc
175
176		// SetHandler sets the matched handler by router.
177		SetHandler(h HandlerFunc)
178
179		// Logger returns the `Logger` instance.
180		Logger() Logger
181
182		// Echo returns the `Echo` instance.
183		Echo() *Echo
184
185		// Reset resets the context after request completes. It must be called along
186		// with `Echo#AcquireContext()` and `Echo#ReleaseContext()`.
187		// See `Echo#ServeHTTP()`
188		Reset(r *http.Request, w http.ResponseWriter)
189	}
190
191	context struct {
192		request  *http.Request
193		response *Response
194		path     string
195		pnames   []string
196		pvalues  []string
197		query    url.Values
198		handler  HandlerFunc
199		store    Map
200		echo     *Echo
201	}
202)
203
204const (
205	defaultMemory = 32 << 20 // 32 MB
206	indexPage     = "index.html"
207	defaultIndent = "  "
208)
209
210func (c *context) writeContentType(value string) {
211	header := c.Response().Header()
212	if header.Get(HeaderContentType) == "" {
213		header.Set(HeaderContentType, value)
214	}
215}
216
217func (c *context) Request() *http.Request {
218	return c.request
219}
220
221func (c *context) SetRequest(r *http.Request) {
222	c.request = r
223}
224
225func (c *context) Response() *Response {
226	return c.response
227}
228
229func (c *context) IsTLS() bool {
230	return c.request.TLS != nil
231}
232
233func (c *context) IsWebSocket() bool {
234	upgrade := c.request.Header.Get(HeaderUpgrade)
235	return upgrade == "websocket" || upgrade == "Websocket"
236}
237
238func (c *context) Scheme() string {
239	// Can't use `r.Request.URL.Scheme`
240	// See: https://groups.google.com/forum/#!topic/golang-nuts/pMUkBlQBDF0
241	if c.IsTLS() {
242		return "https"
243	}
244	if scheme := c.request.Header.Get(HeaderXForwardedProto); scheme != "" {
245		return scheme
246	}
247	if scheme := c.request.Header.Get(HeaderXForwardedProtocol); scheme != "" {
248		return scheme
249	}
250	if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" {
251		return "https"
252	}
253	if scheme := c.request.Header.Get(HeaderXUrlScheme); scheme != "" {
254		return scheme
255	}
256	return "http"
257}
258
259func (c *context) RealIP() string {
260	if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
261		return strings.Split(ip, ", ")[0]
262	}
263	if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
264		return ip
265	}
266	ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
267	return ra
268}
269
270func (c *context) Path() string {
271	return c.path
272}
273
274func (c *context) SetPath(p string) {
275	c.path = p
276}
277
278func (c *context) Param(name string) string {
279	for i, n := range c.pnames {
280		if i < len(c.pvalues) {
281			if n == name {
282				return c.pvalues[i]
283			}
284		}
285	}
286	return ""
287}
288
289func (c *context) ParamNames() []string {
290	return c.pnames
291}
292
293func (c *context) SetParamNames(names ...string) {
294	c.pnames = names
295}
296
297func (c *context) ParamValues() []string {
298	return c.pvalues[:len(c.pnames)]
299}
300
301func (c *context) SetParamValues(values ...string) {
302	c.pvalues = values
303}
304
305func (c *context) QueryParam(name string) string {
306	if c.query == nil {
307		c.query = c.request.URL.Query()
308	}
309	return c.query.Get(name)
310}
311
312func (c *context) QueryParams() url.Values {
313	if c.query == nil {
314		c.query = c.request.URL.Query()
315	}
316	return c.query
317}
318
319func (c *context) QueryString() string {
320	return c.request.URL.RawQuery
321}
322
323func (c *context) FormValue(name string) string {
324	return c.request.FormValue(name)
325}
326
327func (c *context) FormParams() (url.Values, error) {
328	if strings.HasPrefix(c.request.Header.Get(HeaderContentType), MIMEMultipartForm) {
329		if err := c.request.ParseMultipartForm(defaultMemory); err != nil {
330			return nil, err
331		}
332	} else {
333		if err := c.request.ParseForm(); err != nil {
334			return nil, err
335		}
336	}
337	return c.request.Form, nil
338}
339
340func (c *context) FormFile(name string) (*multipart.FileHeader, error) {
341	_, fh, err := c.request.FormFile(name)
342	return fh, err
343}
344
345func (c *context) MultipartForm() (*multipart.Form, error) {
346	err := c.request.ParseMultipartForm(defaultMemory)
347	return c.request.MultipartForm, err
348}
349
350func (c *context) Cookie(name string) (*http.Cookie, error) {
351	return c.request.Cookie(name)
352}
353
354func (c *context) SetCookie(cookie *http.Cookie) {
355	http.SetCookie(c.Response(), cookie)
356}
357
358func (c *context) Cookies() []*http.Cookie {
359	return c.request.Cookies()
360}
361
362func (c *context) Get(key string) interface{} {
363	return c.store[key]
364}
365
366func (c *context) Set(key string, val interface{}) {
367	if c.store == nil {
368		c.store = make(Map)
369	}
370	c.store[key] = val
371}
372
373func (c *context) Bind(i interface{}) error {
374	return c.echo.Binder.Bind(i, c)
375}
376
377func (c *context) Validate(i interface{}) error {
378	if c.echo.Validator == nil {
379		return ErrValidatorNotRegistered
380	}
381	return c.echo.Validator.Validate(i)
382}
383
384func (c *context) Render(code int, name string, data interface{}) (err error) {
385	if c.echo.Renderer == nil {
386		return ErrRendererNotRegistered
387	}
388	buf := new(bytes.Buffer)
389	if err = c.echo.Renderer.Render(buf, name, data, c); err != nil {
390		return
391	}
392	return c.HTMLBlob(code, buf.Bytes())
393}
394
395func (c *context) HTML(code int, html string) (err error) {
396	return c.HTMLBlob(code, []byte(html))
397}
398
399func (c *context) HTMLBlob(code int, b []byte) (err error) {
400	return c.Blob(code, MIMETextHTMLCharsetUTF8, b)
401}
402
403func (c *context) String(code int, s string) (err error) {
404	return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
405}
406
407func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
408	enc := json.NewEncoder(c.response)
409	_, pretty := c.QueryParams()["pretty"]
410	if c.echo.Debug || pretty {
411		enc.SetIndent("", "  ")
412	}
413	c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
414	c.response.WriteHeader(code)
415	if _, err = c.response.Write([]byte(callback + "(")); err != nil {
416		return
417	}
418	if err = enc.Encode(i); err != nil {
419		return
420	}
421	if _, err = c.response.Write([]byte(");")); err != nil {
422		return
423	}
424	return
425}
426
427func (c *context) json(code int, i interface{}, indent string) error {
428	enc := json.NewEncoder(c.response)
429	if indent != "" {
430		enc.SetIndent("", indent)
431	}
432	c.writeContentType(MIMEApplicationJSONCharsetUTF8)
433	c.response.WriteHeader(code)
434	return enc.Encode(i)
435}
436
437func (c *context) JSON(code int, i interface{}) (err error) {
438	indent := ""
439	if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
440		indent = defaultIndent
441	}
442	return c.json(code, i, indent)
443}
444
445func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
446	return c.json(code, i, indent)
447}
448
449func (c *context) JSONBlob(code int, b []byte) (err error) {
450	return c.Blob(code, MIMEApplicationJSONCharsetUTF8, b)
451}
452
453func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
454	return c.jsonPBlob(code, callback, i)
455}
456
457func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
458	c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
459	c.response.WriteHeader(code)
460	if _, err = c.response.Write([]byte(callback + "(")); err != nil {
461		return
462	}
463	if _, err = c.response.Write(b); err != nil {
464		return
465	}
466	_, err = c.response.Write([]byte(");"))
467	return
468}
469
470func (c *context) xml(code int, i interface{}, indent string) (err error) {
471	c.writeContentType(MIMEApplicationXMLCharsetUTF8)
472	c.response.WriteHeader(code)
473	enc := xml.NewEncoder(c.response)
474	if indent != "" {
475		enc.Indent("", indent)
476	}
477	if _, err = c.response.Write([]byte(xml.Header)); err != nil {
478		return
479	}
480	return enc.Encode(i)
481}
482
483func (c *context) XML(code int, i interface{}) (err error) {
484	indent := ""
485	if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
486		indent = defaultIndent
487	}
488	return c.xml(code, i, indent)
489}
490
491func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
492	return c.xml(code, i, indent)
493}
494
495func (c *context) XMLBlob(code int, b []byte) (err error) {
496	c.writeContentType(MIMEApplicationXMLCharsetUTF8)
497	c.response.WriteHeader(code)
498	if _, err = c.response.Write([]byte(xml.Header)); err != nil {
499		return
500	}
501	_, err = c.response.Write(b)
502	return
503}
504
505func (c *context) Blob(code int, contentType string, b []byte) (err error) {
506	c.writeContentType(contentType)
507	c.response.WriteHeader(code)
508	_, err = c.response.Write(b)
509	return
510}
511
512func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
513	c.writeContentType(contentType)
514	c.response.WriteHeader(code)
515	_, err = io.Copy(c.response, r)
516	return
517}
518
519func (c *context) File(file string) (err error) {
520	f, err := os.Open(file)
521	if err != nil {
522		return NotFoundHandler(c)
523	}
524	defer f.Close()
525
526	fi, _ := f.Stat()
527	if fi.IsDir() {
528		file = filepath.Join(file, indexPage)
529		f, err = os.Open(file)
530		if err != nil {
531			return NotFoundHandler(c)
532		}
533		defer f.Close()
534		if fi, err = f.Stat(); err != nil {
535			return
536		}
537	}
538	http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
539	return
540}
541
542func (c *context) Attachment(file, name string) error {
543	return c.contentDisposition(file, name, "attachment")
544}
545
546func (c *context) Inline(file, name string) error {
547	return c.contentDisposition(file, name, "inline")
548}
549
550func (c *context) contentDisposition(file, name, dispositionType string) error {
551	c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
552	return c.File(file)
553}
554
555func (c *context) NoContent(code int) error {
556	c.response.WriteHeader(code)
557	return nil
558}
559
560func (c *context) Redirect(code int, url string) error {
561	if code < 300 || code > 308 {
562		return ErrInvalidRedirectCode
563	}
564	c.response.Header().Set(HeaderLocation, url)
565	c.response.WriteHeader(code)
566	return nil
567}
568
569func (c *context) Error(err error) {
570	c.echo.HTTPErrorHandler(err, c)
571}
572
573func (c *context) Echo() *Echo {
574	return c.echo
575}
576
577func (c *context) Handler() HandlerFunc {
578	return c.handler
579}
580
581func (c *context) SetHandler(h HandlerFunc) {
582	c.handler = h
583}
584
585func (c *context) Logger() Logger {
586	return c.echo.Logger
587}
588
589func (c *context) Reset(r *http.Request, w http.ResponseWriter) {
590	c.request = r
591	c.response.reset(w)
592	c.query = nil
593	c.handler = NotFoundHandler
594	c.store = nil
595	c.path = ""
596	c.pnames = nil
597	// NOTE: Don't reset because it has to have length c.echo.maxParam at all times
598	// c.pvalues = nil
599}
600
601