github.com/astaxie/beego@v1.12.3/context/context.go (about)

     1  // Copyright 2014 beego Author. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain 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,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package context provide the context utils
    16  // Usage:
    17  //
    18  //	import "github.com/astaxie/beego/context"
    19  //
    20  //	ctx := context.Context{Request:req,ResponseWriter:rw}
    21  //
    22  //  more docs http://beego.me/docs/module/context.md
    23  package context
    24  
    25  import (
    26  	"bufio"
    27  	"crypto/hmac"
    28  	"crypto/sha256"
    29  	"encoding/base64"
    30  	"errors"
    31  	"fmt"
    32  	"net"
    33  	"net/http"
    34  	"strconv"
    35  	"strings"
    36  	"time"
    37  
    38  	"github.com/astaxie/beego/utils"
    39  )
    40  
    41  //commonly used mime-types
    42  const (
    43  	ApplicationJSON = "application/json"
    44  	ApplicationXML  = "application/xml"
    45  	ApplicationYAML = "application/x-yaml"
    46  	TextXML         = "text/xml"
    47  )
    48  
    49  // NewContext return the Context with Input and Output
    50  func NewContext() *Context {
    51  	return &Context{
    52  		Input:  NewInput(),
    53  		Output: NewOutput(),
    54  	}
    55  }
    56  
    57  // Context Http request context struct including BeegoInput, BeegoOutput, http.Request and http.ResponseWriter.
    58  // BeegoInput and BeegoOutput provides some api to operate request and response more easily.
    59  type Context struct {
    60  	Input          *BeegoInput
    61  	Output         *BeegoOutput
    62  	Request        *http.Request
    63  	ResponseWriter *Response
    64  	_xsrfToken     string
    65  }
    66  
    67  // Reset init Context, BeegoInput and BeegoOutput
    68  func (ctx *Context) Reset(rw http.ResponseWriter, r *http.Request) {
    69  	ctx.Request = r
    70  	if ctx.ResponseWriter == nil {
    71  		ctx.ResponseWriter = &Response{}
    72  	}
    73  	ctx.ResponseWriter.reset(rw)
    74  	ctx.Input.Reset(ctx)
    75  	ctx.Output.Reset(ctx)
    76  	ctx._xsrfToken = ""
    77  }
    78  
    79  // Redirect does redirection to localurl with http header status code.
    80  func (ctx *Context) Redirect(status int, localurl string) {
    81  	http.Redirect(ctx.ResponseWriter, ctx.Request, localurl, status)
    82  }
    83  
    84  // Abort stops this request.
    85  // if beego.ErrorMaps exists, panic body.
    86  func (ctx *Context) Abort(status int, body string) {
    87  	ctx.Output.SetStatus(status)
    88  	panic(body)
    89  }
    90  
    91  // WriteString Write string to response body.
    92  // it sends response body.
    93  func (ctx *Context) WriteString(content string) {
    94  	ctx.ResponseWriter.Write([]byte(content))
    95  }
    96  
    97  // GetCookie Get cookie from request by a given key.
    98  // It's alias of BeegoInput.Cookie.
    99  func (ctx *Context) GetCookie(key string) string {
   100  	return ctx.Input.Cookie(key)
   101  }
   102  
   103  // SetCookie Set cookie for response.
   104  // It's alias of BeegoOutput.Cookie.
   105  func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
   106  	ctx.Output.Cookie(name, value, others...)
   107  }
   108  
   109  // GetSecureCookie Get secure cookie from request by a given key.
   110  func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
   111  	val := ctx.Input.Cookie(key)
   112  	if val == "" {
   113  		return "", false
   114  	}
   115  
   116  	parts := strings.SplitN(val, "|", 3)
   117  
   118  	if len(parts) != 3 {
   119  		return "", false
   120  	}
   121  
   122  	vs := parts[0]
   123  	timestamp := parts[1]
   124  	sig := parts[2]
   125  
   126  	h := hmac.New(sha256.New, []byte(Secret))
   127  	fmt.Fprintf(h, "%s%s", vs, timestamp)
   128  
   129  	if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
   130  		return "", false
   131  	}
   132  	res, _ := base64.URLEncoding.DecodeString(vs)
   133  	return string(res), true
   134  }
   135  
   136  // SetSecureCookie Set Secure cookie for response.
   137  func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
   138  	vs := base64.URLEncoding.EncodeToString([]byte(value))
   139  	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
   140  	h := hmac.New(sha256.New, []byte(Secret))
   141  	fmt.Fprintf(h, "%s%s", vs, timestamp)
   142  	sig := fmt.Sprintf("%02x", h.Sum(nil))
   143  	cookie := strings.Join([]string{vs, timestamp, sig}, "|")
   144  	ctx.Output.Cookie(name, cookie, others...)
   145  }
   146  
   147  // XSRFToken creates a xsrf token string and returns.
   148  func (ctx *Context) XSRFToken(key string, expire int64) string {
   149  	if ctx._xsrfToken == "" {
   150  		token, ok := ctx.GetSecureCookie(key, "_xsrf")
   151  		if !ok {
   152  			token = string(utils.RandomCreateBytes(32))
   153  			ctx.SetSecureCookie(key, "_xsrf", token, expire, "", "", true, true)
   154  		}
   155  		ctx._xsrfToken = token
   156  	}
   157  	return ctx._xsrfToken
   158  }
   159  
   160  // CheckXSRFCookie checks xsrf token in this request is valid or not.
   161  // the token can provided in request header "X-Xsrftoken" and "X-CsrfToken"
   162  // or in form field value named as "_xsrf".
   163  func (ctx *Context) CheckXSRFCookie() bool {
   164  	token := ctx.Input.Query("_xsrf")
   165  	if token == "" {
   166  		token = ctx.Request.Header.Get("X-Xsrftoken")
   167  	}
   168  	if token == "" {
   169  		token = ctx.Request.Header.Get("X-Csrftoken")
   170  	}
   171  	if token == "" {
   172  		ctx.Abort(422, "422")
   173  		return false
   174  	}
   175  	if ctx._xsrfToken != token {
   176  		ctx.Abort(417, "417")
   177  		return false
   178  	}
   179  	return true
   180  }
   181  
   182  // RenderMethodResult renders the return value of a controller method to the output
   183  func (ctx *Context) RenderMethodResult(result interface{}) {
   184  	if result != nil {
   185  		renderer, ok := result.(Renderer)
   186  		if !ok {
   187  			err, ok := result.(error)
   188  			if ok {
   189  				renderer = errorRenderer(err)
   190  			} else {
   191  				renderer = jsonRenderer(result)
   192  			}
   193  		}
   194  		renderer.Render(ctx)
   195  	}
   196  }
   197  
   198  //Response is a wrapper for the http.ResponseWriter
   199  //started set to true if response was written to then don't execute other handler
   200  type Response struct {
   201  	http.ResponseWriter
   202  	Started bool
   203  	Status  int
   204  	Elapsed time.Duration
   205  }
   206  
   207  func (r *Response) reset(rw http.ResponseWriter) {
   208  	r.ResponseWriter = rw
   209  	r.Status = 0
   210  	r.Started = false
   211  }
   212  
   213  // Write writes the data to the connection as part of an HTTP reply,
   214  // and sets `started` to true.
   215  // started means the response has sent out.
   216  func (r *Response) Write(p []byte) (int, error) {
   217  	r.Started = true
   218  	return r.ResponseWriter.Write(p)
   219  }
   220  
   221  // WriteHeader sends an HTTP response header with status code,
   222  // and sets `started` to true.
   223  func (r *Response) WriteHeader(code int) {
   224  	if r.Status > 0 {
   225  		//prevent multiple response.WriteHeader calls
   226  		return
   227  	}
   228  	r.Status = code
   229  	r.Started = true
   230  	r.ResponseWriter.WriteHeader(code)
   231  }
   232  
   233  // Hijack hijacker for http
   234  func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   235  	hj, ok := r.ResponseWriter.(http.Hijacker)
   236  	if !ok {
   237  		return nil, nil, errors.New("webserver doesn't support hijacking")
   238  	}
   239  	return hj.Hijack()
   240  }
   241  
   242  // Flush http.Flusher
   243  func (r *Response) Flush() {
   244  	if f, ok := r.ResponseWriter.(http.Flusher); ok {
   245  		f.Flush()
   246  	}
   247  }
   248  
   249  // CloseNotify http.CloseNotifier
   250  func (r *Response) CloseNotify() <-chan bool {
   251  	if cn, ok := r.ResponseWriter.(http.CloseNotifier); ok {
   252  		return cn.CloseNotify()
   253  	}
   254  	return nil
   255  }
   256  
   257  // Pusher http.Pusher
   258  func (r *Response) Pusher() (pusher http.Pusher) {
   259  	if pusher, ok := r.ResponseWriter.(http.Pusher); ok {
   260  		return pusher
   261  	}
   262  	return nil
   263  }