diff options
Diffstat (limited to 'vendor/github.com/gin-gonic/gin/context.go')
-rw-r--r-- | vendor/github.com/gin-gonic/gin/context.go | 1267 |
1 files changed, 0 insertions, 1267 deletions
diff --git a/vendor/github.com/gin-gonic/gin/context.go b/vendor/github.com/gin-gonic/gin/context.go deleted file mode 100644 index 391adafed..000000000 --- a/vendor/github.com/gin-gonic/gin/context.go +++ /dev/null @@ -1,1267 +0,0 @@ -// Copyright 2014 Manu Martinez-Almeida. All rights reserved. -// Use of this source code is governed by a MIT style -// license that can be found in the LICENSE file. - -package gin - -import ( - "errors" - "io" - "log" - "math" - "mime/multipart" - "net" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/gin-contrib/sse" - "github.com/gin-gonic/gin/binding" - "github.com/gin-gonic/gin/render" -) - -// Content-Type MIME of the most common data formats. -const ( - MIMEJSON = binding.MIMEJSON - MIMEHTML = binding.MIMEHTML - MIMEXML = binding.MIMEXML - MIMEXML2 = binding.MIMEXML2 - MIMEPlain = binding.MIMEPlain - MIMEPOSTForm = binding.MIMEPOSTForm - MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm - MIMEYAML = binding.MIMEYAML - MIMETOML = binding.MIMETOML -) - -// BodyBytesKey indicates a default body bytes key. -const BodyBytesKey = "_gin-gonic/gin/bodybyteskey" - -// ContextKey is the key that a Context returns itself for. -const ContextKey = "_gin-gonic/gin/contextkey" - -type ContextKeyType int - -const ContextRequestKey ContextKeyType = 0 - -// abortIndex represents a typical value used in abort functions. -const abortIndex int8 = math.MaxInt8 >> 1 - -// Context is the most important part of gin. It allows us to pass variables between middleware, -// manage the flow, validate the JSON of a request and render a JSON response for example. -type Context struct { - writermem responseWriter - Request *http.Request - Writer ResponseWriter - - Params Params - handlers HandlersChain - index int8 - fullPath string - - engine *Engine - params *Params - skippedNodes *[]skippedNode - - // This mutex protects Keys map. - mu sync.RWMutex - - // Keys is a key/value pair exclusively for the context of each request. - Keys map[string]any - - // Errors is a list of errors attached to all the handlers/middlewares who used this context. - Errors errorMsgs - - // Accepted defines a list of manually accepted formats for content negotiation. - Accepted []string - - // queryCache caches the query result from c.Request.URL.Query(). - queryCache url.Values - - // formCache caches c.Request.PostForm, which contains the parsed form data from POST, PATCH, - // or PUT body parameters. - formCache url.Values - - // SameSite allows a server to define a cookie attribute making it impossible for - // the browser to send this cookie along with cross-site requests. - sameSite http.SameSite -} - -/************************************/ -/********** CONTEXT CREATION ********/ -/************************************/ - -func (c *Context) reset() { - c.Writer = &c.writermem - c.Params = c.Params[:0] - c.handlers = nil - c.index = -1 - - c.fullPath = "" - c.Keys = nil - c.Errors = c.Errors[:0] - c.Accepted = nil - c.queryCache = nil - c.formCache = nil - c.sameSite = 0 - *c.params = (*c.params)[:0] - *c.skippedNodes = (*c.skippedNodes)[:0] -} - -// Copy returns a copy of the current context that can be safely used outside the request's scope. -// This has to be used when the context has to be passed to a goroutine. -func (c *Context) Copy() *Context { - cp := Context{ - writermem: c.writermem, - Request: c.Request, - engine: c.engine, - } - - cp.writermem.ResponseWriter = nil - cp.Writer = &cp.writermem - cp.index = abortIndex - cp.handlers = nil - cp.fullPath = c.fullPath - - cKeys := c.Keys - cp.Keys = make(map[string]any, len(cKeys)) - c.mu.RLock() - for k, v := range cKeys { - cp.Keys[k] = v - } - c.mu.RUnlock() - - cParams := c.Params - cp.Params = make([]Param, len(cParams)) - copy(cp.Params, cParams) - - return &cp -} - -// HandlerName returns the main handler's name. For example if the handler is "handleGetUsers()", -// this function will return "main.handleGetUsers". -func (c *Context) HandlerName() string { - return nameOfFunction(c.handlers.Last()) -} - -// HandlerNames returns a list of all registered handlers for this context in descending order, -// following the semantics of HandlerName() -func (c *Context) HandlerNames() []string { - hn := make([]string, 0, len(c.handlers)) - for _, val := range c.handlers { - hn = append(hn, nameOfFunction(val)) - } - return hn -} - -// Handler returns the main handler. -func (c *Context) Handler() HandlerFunc { - return c.handlers.Last() -} - -// FullPath returns a matched route full path. For not found routes -// returns an empty string. -// -// router.GET("/user/:id", func(c *gin.Context) { -// c.FullPath() == "/user/:id" // true -// }) -func (c *Context) FullPath() string { - return c.fullPath -} - -/************************************/ -/*********** FLOW CONTROL ***********/ -/************************************/ - -// Next should be used only inside middleware. -// It executes the pending handlers in the chain inside the calling handler. -// See example in GitHub. -func (c *Context) Next() { - c.index++ - for c.index < int8(len(c.handlers)) { - c.handlers[c.index](c) - c.index++ - } -} - -// IsAborted returns true if the current context was aborted. -func (c *Context) IsAborted() bool { - return c.index >= abortIndex -} - -// Abort prevents pending handlers from being called. Note that this will not stop the current handler. -// Let's say you have an authorization middleware that validates that the current request is authorized. -// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers -// for this request are not called. -func (c *Context) Abort() { - c.index = abortIndex -} - -// AbortWithStatus calls `Abort()` and writes the headers with the specified status code. -// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401). -func (c *Context) AbortWithStatus(code int) { - c.Status(code) - c.Writer.WriteHeaderNow() - c.Abort() -} - -// AbortWithStatusJSON calls `Abort()` and then `JSON` internally. -// This method stops the chain, writes the status code and return a JSON body. -// It also sets the Content-Type as "application/json". -func (c *Context) AbortWithStatusJSON(code int, jsonObj any) { - c.Abort() - c.JSON(code, jsonObj) -} - -// AbortWithError calls `AbortWithStatus()` and `Error()` internally. -// This method stops the chain, writes the status code and pushes the specified error to `c.Errors`. -// See Context.Error() for more details. -func (c *Context) AbortWithError(code int, err error) *Error { - c.AbortWithStatus(code) - return c.Error(err) -} - -/************************************/ -/********* ERROR MANAGEMENT *********/ -/************************************/ - -// Error attaches an error to the current context. The error is pushed to a list of errors. -// It's a good idea to call Error for each error that occurred during the resolution of a request. -// A middleware can be used to collect all the errors and push them to a database together, -// print a log, or append it in the HTTP response. -// Error will panic if err is nil. -func (c *Context) Error(err error) *Error { - if err == nil { - panic("err is nil") - } - - var parsedError *Error - ok := errors.As(err, &parsedError) - if !ok { - parsedError = &Error{ - Err: err, - Type: ErrorTypePrivate, - } - } - - c.Errors = append(c.Errors, parsedError) - return parsedError -} - -/************************************/ -/******** METADATA MANAGEMENT********/ -/************************************/ - -// Set is used to store a new key/value pair exclusively for this context. -// It also lazy initializes c.Keys if it was not used previously. -func (c *Context) Set(key string, value any) { - c.mu.Lock() - defer c.mu.Unlock() - if c.Keys == nil { - c.Keys = make(map[string]any) - } - - c.Keys[key] = value -} - -// Get returns the value for the given key, ie: (value, true). -// If the value does not exist it returns (nil, false) -func (c *Context) Get(key string) (value any, exists bool) { - c.mu.RLock() - defer c.mu.RUnlock() - value, exists = c.Keys[key] - return -} - -// MustGet returns the value for the given key if it exists, otherwise it panics. -func (c *Context) MustGet(key string) any { - if value, exists := c.Get(key); exists { - return value - } - panic("Key \"" + key + "\" does not exist") -} - -// GetString returns the value associated with the key as a string. -func (c *Context) GetString(key string) (s string) { - if val, ok := c.Get(key); ok && val != nil { - s, _ = val.(string) - } - return -} - -// GetBool returns the value associated with the key as a boolean. -func (c *Context) GetBool(key string) (b bool) { - if val, ok := c.Get(key); ok && val != nil { - b, _ = val.(bool) - } - return -} - -// GetInt returns the value associated with the key as an integer. -func (c *Context) GetInt(key string) (i int) { - if val, ok := c.Get(key); ok && val != nil { - i, _ = val.(int) - } - return -} - -// GetInt64 returns the value associated with the key as an integer. -func (c *Context) GetInt64(key string) (i64 int64) { - if val, ok := c.Get(key); ok && val != nil { - i64, _ = val.(int64) - } - return -} - -// GetUint returns the value associated with the key as an unsigned integer. -func (c *Context) GetUint(key string) (ui uint) { - if val, ok := c.Get(key); ok && val != nil { - ui, _ = val.(uint) - } - return -} - -// GetUint64 returns the value associated with the key as an unsigned integer. -func (c *Context) GetUint64(key string) (ui64 uint64) { - if val, ok := c.Get(key); ok && val != nil { - ui64, _ = val.(uint64) - } - return -} - -// GetFloat64 returns the value associated with the key as a float64. -func (c *Context) GetFloat64(key string) (f64 float64) { - if val, ok := c.Get(key); ok && val != nil { - f64, _ = val.(float64) - } - return -} - -// GetTime returns the value associated with the key as time. -func (c *Context) GetTime(key string) (t time.Time) { - if val, ok := c.Get(key); ok && val != nil { - t, _ = val.(time.Time) - } - return -} - -// GetDuration returns the value associated with the key as a duration. -func (c *Context) GetDuration(key string) (d time.Duration) { - if val, ok := c.Get(key); ok && val != nil { - d, _ = val.(time.Duration) - } - return -} - -// GetStringSlice returns the value associated with the key as a slice of strings. -func (c *Context) GetStringSlice(key string) (ss []string) { - if val, ok := c.Get(key); ok && val != nil { - ss, _ = val.([]string) - } - return -} - -// GetStringMap returns the value associated with the key as a map of interfaces. -func (c *Context) GetStringMap(key string) (sm map[string]any) { - if val, ok := c.Get(key); ok && val != nil { - sm, _ = val.(map[string]any) - } - return -} - -// GetStringMapString returns the value associated with the key as a map of strings. -func (c *Context) GetStringMapString(key string) (sms map[string]string) { - if val, ok := c.Get(key); ok && val != nil { - sms, _ = val.(map[string]string) - } - return -} - -// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. -func (c *Context) GetStringMapStringSlice(key string) (smss map[string][]string) { - if val, ok := c.Get(key); ok && val != nil { - smss, _ = val.(map[string][]string) - } - return -} - -/************************************/ -/************ INPUT DATA ************/ -/************************************/ - -// Param returns the value of the URL param. -// It is a shortcut for c.Params.ByName(key) -// -// router.GET("/user/:id", func(c *gin.Context) { -// // a GET request to /user/john -// id := c.Param("id") // id == "john" -// // a GET request to /user/john/ -// id := c.Param("id") // id == "/john/" -// }) -func (c *Context) Param(key string) string { - return c.Params.ByName(key) -} - -// AddParam adds param to context and -// replaces path param key with given value for e2e testing purposes -// Example Route: "/user/:id" -// AddParam("id", 1) -// Result: "/user/1" -func (c *Context) AddParam(key, value string) { - c.Params = append(c.Params, Param{Key: key, Value: value}) -} - -// Query returns the keyed url query value if it exists, -// otherwise it returns an empty string `("")`. -// It is shortcut for `c.Request.URL.Query().Get(key)` -// -// GET /path?id=1234&name=Manu&value= -// c.Query("id") == "1234" -// c.Query("name") == "Manu" -// c.Query("value") == "" -// c.Query("wtf") == "" -func (c *Context) Query(key string) (value string) { - value, _ = c.GetQuery(key) - return -} - -// DefaultQuery returns the keyed url query value if it exists, -// otherwise it returns the specified defaultValue string. -// See: Query() and GetQuery() for further information. -// -// GET /?name=Manu&lastname= -// c.DefaultQuery("name", "unknown") == "Manu" -// c.DefaultQuery("id", "none") == "none" -// c.DefaultQuery("lastname", "none") == "" -func (c *Context) DefaultQuery(key, defaultValue string) string { - if value, ok := c.GetQuery(key); ok { - return value - } - return defaultValue -} - -// GetQuery is like Query(), it returns the keyed url query value -// if it exists `(value, true)` (even when the value is an empty string), -// otherwise it returns `("", false)`. -// It is shortcut for `c.Request.URL.Query().Get(key)` -// -// GET /?name=Manu&lastname= -// ("Manu", true) == c.GetQuery("name") -// ("", false) == c.GetQuery("id") -// ("", true) == c.GetQuery("lastname") -func (c *Context) GetQuery(key string) (string, bool) { - if values, ok := c.GetQueryArray(key); ok { - return values[0], ok - } - return "", false -} - -// QueryArray returns a slice of strings for a given query key. -// The length of the slice depends on the number of params with the given key. -func (c *Context) QueryArray(key string) (values []string) { - values, _ = c.GetQueryArray(key) - return -} - -func (c *Context) initQueryCache() { - if c.queryCache == nil { - if c.Request != nil { - c.queryCache = c.Request.URL.Query() - } else { - c.queryCache = url.Values{} - } - } -} - -// GetQueryArray returns a slice of strings for a given query key, plus -// a boolean value whether at least one value exists for the given key. -func (c *Context) GetQueryArray(key string) (values []string, ok bool) { - c.initQueryCache() - values, ok = c.queryCache[key] - return -} - -// QueryMap returns a map for a given query key. -func (c *Context) QueryMap(key string) (dicts map[string]string) { - dicts, _ = c.GetQueryMap(key) - return -} - -// GetQueryMap returns a map for a given query key, plus a boolean value -// whether at least one value exists for the given key. -func (c *Context) GetQueryMap(key string) (map[string]string, bool) { - c.initQueryCache() - return c.get(c.queryCache, key) -} - -// PostForm returns the specified key from a POST urlencoded form or multipart form -// when it exists, otherwise it returns an empty string `("")`. -func (c *Context) PostForm(key string) (value string) { - value, _ = c.GetPostForm(key) - return -} - -// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form -// when it exists, otherwise it returns the specified defaultValue string. -// See: PostForm() and GetPostForm() for further information. -func (c *Context) DefaultPostForm(key, defaultValue string) string { - if value, ok := c.GetPostForm(key); ok { - return value - } - return defaultValue -} - -// GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded -// form or multipart form when it exists `(value, true)` (even when the value is an empty string), -// otherwise it returns ("", false). -// For example, during a PATCH request to update the user's email: -// -// email=mail@example.com --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com" -// email= --> ("", true) := GetPostForm("email") // set email to "" -// --> ("", false) := GetPostForm("email") // do nothing with email -func (c *Context) GetPostForm(key string) (string, bool) { - if values, ok := c.GetPostFormArray(key); ok { - return values[0], ok - } - return "", false -} - -// PostFormArray returns a slice of strings for a given form key. -// The length of the slice depends on the number of params with the given key. -func (c *Context) PostFormArray(key string) (values []string) { - values, _ = c.GetPostFormArray(key) - return -} - -func (c *Context) initFormCache() { - if c.formCache == nil { - c.formCache = make(url.Values) - req := c.Request - if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { - if !errors.Is(err, http.ErrNotMultipart) { - debugPrint("error on parse multipart form array: %v", err) - } - } - c.formCache = req.PostForm - } -} - -// GetPostFormArray returns a slice of strings for a given form key, plus -// a boolean value whether at least one value exists for the given key. -func (c *Context) GetPostFormArray(key string) (values []string, ok bool) { - c.initFormCache() - values, ok = c.formCache[key] - return -} - -// PostFormMap returns a map for a given form key. -func (c *Context) PostFormMap(key string) (dicts map[string]string) { - dicts, _ = c.GetPostFormMap(key) - return -} - -// GetPostFormMap returns a map for a given form key, plus a boolean value -// whether at least one value exists for the given key. -func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { - c.initFormCache() - return c.get(c.formCache, key) -} - -// get is an internal method and returns a map which satisfies conditions. -func (c *Context) get(m map[string][]string, key string) (map[string]string, bool) { - dicts := make(map[string]string) - exist := false - for k, v := range m { - if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key { - if j := strings.IndexByte(k[i+1:], ']'); j >= 1 { - exist = true - dicts[k[i+1:][:j]] = v[0] - } - } - } - return dicts, exist -} - -// FormFile returns the first file for the provided form key. -func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { - if c.Request.MultipartForm == nil { - if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil { - return nil, err - } - } - f, fh, err := c.Request.FormFile(name) - if err != nil { - return nil, err - } - f.Close() - return fh, err -} - -// MultipartForm is the parsed multipart form, including file uploads. -func (c *Context) MultipartForm() (*multipart.Form, error) { - err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory) - return c.Request.MultipartForm, err -} - -// SaveUploadedFile uploads the form file to specific dst. -func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error { - src, err := file.Open() - if err != nil { - return err - } - defer src.Close() - - if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { - return err - } - - out, err := os.Create(dst) - if err != nil { - return err - } - defer out.Close() - - _, err = io.Copy(out, src) - return err -} - -// Bind checks the Method and Content-Type to select a binding engine automatically, -// Depending on the "Content-Type" header different bindings are used, for example: -// -// "application/json" --> JSON binding -// "application/xml" --> XML binding -// -// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. -// It decodes the json payload into the struct specified as a pointer. -// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid. -func (c *Context) Bind(obj any) error { - b := binding.Default(c.Request.Method, c.ContentType()) - return c.MustBindWith(obj, b) -} - -// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). -func (c *Context) BindJSON(obj any) error { - return c.MustBindWith(obj, binding.JSON) -} - -// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). -func (c *Context) BindXML(obj any) error { - return c.MustBindWith(obj, binding.XML) -} - -// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). -func (c *Context) BindQuery(obj any) error { - return c.MustBindWith(obj, binding.Query) -} - -// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). -func (c *Context) BindYAML(obj any) error { - return c.MustBindWith(obj, binding.YAML) -} - -// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML). -func (c *Context) BindTOML(obj any) error { - return c.MustBindWith(obj, binding.TOML) -} - -// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header). -func (c *Context) BindHeader(obj any) error { - return c.MustBindWith(obj, binding.Header) -} - -// BindUri binds the passed struct pointer using binding.Uri. -// It will abort the request with HTTP 400 if any error occurs. -func (c *Context) BindUri(obj any) error { - if err := c.ShouldBindUri(obj); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck - return err - } - return nil -} - -// MustBindWith binds the passed struct pointer using the specified binding engine. -// It will abort the request with HTTP 400 if any error occurs. -// See the binding package. -func (c *Context) MustBindWith(obj any, b binding.Binding) error { - if err := c.ShouldBindWith(obj, b); err != nil { - c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck - return err - } - return nil -} - -// ShouldBind checks the Method and Content-Type to select a binding engine automatically, -// Depending on the "Content-Type" header different bindings are used, for example: -// -// "application/json" --> JSON binding -// "application/xml" --> XML binding -// -// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input. -// It decodes the json payload into the struct specified as a pointer. -// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid. -func (c *Context) ShouldBind(obj any) error { - b := binding.Default(c.Request.Method, c.ContentType()) - return c.ShouldBindWith(obj, b) -} - -// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). -func (c *Context) ShouldBindJSON(obj any) error { - return c.ShouldBindWith(obj, binding.JSON) -} - -// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). -func (c *Context) ShouldBindXML(obj any) error { - return c.ShouldBindWith(obj, binding.XML) -} - -// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). -func (c *Context) ShouldBindQuery(obj any) error { - return c.ShouldBindWith(obj, binding.Query) -} - -// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). -func (c *Context) ShouldBindYAML(obj any) error { - return c.ShouldBindWith(obj, binding.YAML) -} - -// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML). -func (c *Context) ShouldBindTOML(obj any) error { - return c.ShouldBindWith(obj, binding.TOML) -} - -// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header). -func (c *Context) ShouldBindHeader(obj any) error { - return c.ShouldBindWith(obj, binding.Header) -} - -// ShouldBindUri binds the passed struct pointer using the specified binding engine. -func (c *Context) ShouldBindUri(obj any) error { - m := make(map[string][]string, len(c.Params)) - for _, v := range c.Params { - m[v.Key] = []string{v.Value} - } - return binding.Uri.BindUri(m, obj) -} - -// ShouldBindWith binds the passed struct pointer using the specified binding engine. -// See the binding package. -func (c *Context) ShouldBindWith(obj any, b binding.Binding) error { - return b.Bind(c.Request, obj) -} - -// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request -// body into the context, and reuse when it is called again. -// -// NOTE: This method reads the body before binding. So you should use -// ShouldBindWith for better performance if you need to call only once. -func (c *Context) ShouldBindBodyWith(obj any, bb binding.BindingBody) (err error) { - var body []byte - if cb, ok := c.Get(BodyBytesKey); ok { - if cbb, ok := cb.([]byte); ok { - body = cbb - } - } - if body == nil { - body, err = io.ReadAll(c.Request.Body) - if err != nil { - return err - } - c.Set(BodyBytesKey, body) - } - return bb.BindBody(body, obj) -} - -// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON). -func (c *Context) ShouldBindBodyWithJSON(obj any) error { - return c.ShouldBindBodyWith(obj, binding.JSON) -} - -// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML). -func (c *Context) ShouldBindBodyWithXML(obj any) error { - return c.ShouldBindBodyWith(obj, binding.XML) -} - -// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML). -func (c *Context) ShouldBindBodyWithYAML(obj any) error { - return c.ShouldBindBodyWith(obj, binding.YAML) -} - -// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML). -func (c *Context) ShouldBindBodyWithTOML(obj any) error { - return c.ShouldBindBodyWith(obj, binding.TOML) -} - -// ClientIP implements one best effort algorithm to return the real client IP. -// It calls c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not. -// If it is it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]). -// If the headers are not syntactically valid OR the remote IP does not correspond to a trusted proxy, -// the remote IP (coming from Request.RemoteAddr) is returned. -func (c *Context) ClientIP() string { - // Check if we're running on a trusted platform, continue running backwards if error - if c.engine.TrustedPlatform != "" { - // Developers can define their own header of Trusted Platform or use predefined constants - if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" { - return addr - } - } - - // Legacy "AppEngine" flag - if c.engine.AppEngine { - log.Println(`The AppEngine flag is going to be deprecated. Please check issues #2723 and #2739 and use 'TrustedPlatform: gin.PlatformGoogleAppEngine' instead.`) - if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" { - return addr - } - } - - // It also checks if the remoteIP is a trusted proxy or not. - // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks - // defined by Engine.SetTrustedProxies() - remoteIP := net.ParseIP(c.RemoteIP()) - if remoteIP == nil { - return "" - } - trusted := c.engine.isTrustedProxy(remoteIP) - - if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil { - for _, headerName := range c.engine.RemoteIPHeaders { - ip, valid := c.engine.validateHeader(c.requestHeader(headerName)) - if valid { - return ip - } - } - } - return remoteIP.String() -} - -// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port). -func (c *Context) RemoteIP() string { - ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)) - if err != nil { - return "" - } - return ip -} - -// ContentType returns the Content-Type header of the request. -func (c *Context) ContentType() string { - return filterFlags(c.requestHeader("Content-Type")) -} - -// IsWebsocket returns true if the request headers indicate that a websocket -// handshake is being initiated by the client. -func (c *Context) IsWebsocket() bool { - if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") && - strings.EqualFold(c.requestHeader("Upgrade"), "websocket") { - return true - } - return false -} - -func (c *Context) requestHeader(key string) string { - return c.Request.Header.Get(key) -} - -/************************************/ -/******** RESPONSE RENDERING ********/ -/************************************/ - -// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function. -func bodyAllowedForStatus(status int) bool { - switch { - case status >= 100 && status <= 199: - return false - case status == http.StatusNoContent: - return false - case status == http.StatusNotModified: - return false - } - return true -} - -// Status sets the HTTP response code. -func (c *Context) Status(code int) { - c.Writer.WriteHeader(code) -} - -// Header is an intelligent shortcut for c.Writer.Header().Set(key, value). -// It writes a header in the response. -// If value == "", this method removes the header `c.Writer.Header().Del(key)` -func (c *Context) Header(key, value string) { - if value == "" { - c.Writer.Header().Del(key) - return - } - c.Writer.Header().Set(key, value) -} - -// GetHeader returns value from request headers. -func (c *Context) GetHeader(key string) string { - return c.requestHeader(key) -} - -// GetRawData returns stream data. -func (c *Context) GetRawData() ([]byte, error) { - if c.Request.Body == nil { - return nil, errors.New("cannot read nil body") - } - return io.ReadAll(c.Request.Body) -} - -// SetSameSite with cookie -func (c *Context) SetSameSite(samesite http.SameSite) { - c.sameSite = samesite -} - -// SetCookie adds a Set-Cookie header to the ResponseWriter's headers. -// The provided cookie must have a valid Name. Invalid cookies may be -// silently dropped. -func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) { - if path == "" { - path = "/" - } - http.SetCookie(c.Writer, &http.Cookie{ - Name: name, - Value: url.QueryEscape(value), - MaxAge: maxAge, - Path: path, - Domain: domain, - SameSite: c.sameSite, - Secure: secure, - HttpOnly: httpOnly, - }) -} - -// Cookie returns the named cookie provided in the request or -// ErrNoCookie if not found. And return the named cookie is unescaped. -// If multiple cookies match the given name, only one cookie will -// be returned. -func (c *Context) Cookie(name string) (string, error) { - cookie, err := c.Request.Cookie(name) - if err != nil { - return "", err - } - val, _ := url.QueryUnescape(cookie.Value) - return val, nil -} - -// Render writes the response headers and calls render.Render to render data. -func (c *Context) Render(code int, r render.Render) { - c.Status(code) - - if !bodyAllowedForStatus(code) { - r.WriteContentType(c.Writer) - c.Writer.WriteHeaderNow() - return - } - - if err := r.Render(c.Writer); err != nil { - // Pushing error to c.Errors - _ = c.Error(err) - c.Abort() - } -} - -// HTML renders the HTTP template specified by its file name. -// It also updates the HTTP code and sets the Content-Type as "text/html". -// See http://golang.org/doc/articles/wiki/ -func (c *Context) HTML(code int, name string, obj any) { - instance := c.engine.HTMLRender.Instance(name, obj) - c.Render(code, instance) -} - -// IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body. -// It also sets the Content-Type as "application/json". -// WARNING: we recommend using this only for development purposes since printing pretty JSON is -// more CPU and bandwidth consuming. Use Context.JSON() instead. -func (c *Context) IndentedJSON(code int, obj any) { - c.Render(code, render.IndentedJSON{Data: obj}) -} - -// SecureJSON serializes the given struct as Secure JSON into the response body. -// Default prepends "while(1)," to response body if the given struct is array values. -// It also sets the Content-Type as "application/json". -func (c *Context) SecureJSON(code int, obj any) { - c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj}) -} - -// JSONP serializes the given struct as JSON into the response body. -// It adds padding to response body to request data from a server residing in a different domain than the client. -// It also sets the Content-Type as "application/javascript". -func (c *Context) JSONP(code int, obj any) { - callback := c.DefaultQuery("callback", "") - if callback == "" { - c.Render(code, render.JSON{Data: obj}) - return - } - c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) -} - -// JSON serializes the given struct as JSON into the response body. -// It also sets the Content-Type as "application/json". -func (c *Context) JSON(code int, obj any) { - c.Render(code, render.JSON{Data: obj}) -} - -// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. -// It also sets the Content-Type as "application/json". -func (c *Context) AsciiJSON(code int, obj any) { - c.Render(code, render.AsciiJSON{Data: obj}) -} - -// PureJSON serializes the given struct as JSON into the response body. -// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. -func (c *Context) PureJSON(code int, obj any) { - c.Render(code, render.PureJSON{Data: obj}) -} - -// XML serializes the given struct as XML into the response body. -// It also sets the Content-Type as "application/xml". -func (c *Context) XML(code int, obj any) { - c.Render(code, render.XML{Data: obj}) -} - -// YAML serializes the given struct as YAML into the response body. -func (c *Context) YAML(code int, obj any) { - c.Render(code, render.YAML{Data: obj}) -} - -// TOML serializes the given struct as TOML into the response body. -func (c *Context) TOML(code int, obj any) { - c.Render(code, render.TOML{Data: obj}) -} - -// ProtoBuf serializes the given struct as ProtoBuf into the response body. -func (c *Context) ProtoBuf(code int, obj any) { - c.Render(code, render.ProtoBuf{Data: obj}) -} - -// String writes the given string into the response body. -func (c *Context) String(code int, format string, values ...any) { - c.Render(code, render.String{Format: format, Data: values}) -} - -// Redirect returns an HTTP redirect to the specific location. -func (c *Context) Redirect(code int, location string) { - c.Render(-1, render.Redirect{ - Code: code, - Location: location, - Request: c.Request, - }) -} - -// Data writes some data into the body stream and updates the HTTP code. -func (c *Context) Data(code int, contentType string, data []byte) { - c.Render(code, render.Data{ - ContentType: contentType, - Data: data, - }) -} - -// DataFromReader writes the specified reader into the body stream and updates the HTTP code. -func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) { - c.Render(code, render.Reader{ - Headers: extraHeaders, - ContentType: contentType, - ContentLength: contentLength, - Reader: reader, - }) -} - -// File writes the specified file into the body stream in an efficient way. -func (c *Context) File(filepath string) { - http.ServeFile(c.Writer, c.Request, filepath) -} - -// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way. -func (c *Context) FileFromFS(filepath string, fs http.FileSystem) { - defer func(old string) { - c.Request.URL.Path = old - }(c.Request.URL.Path) - - c.Request.URL.Path = filepath - - http.FileServer(fs).ServeHTTP(c.Writer, c.Request) -} - -var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") - -func escapeQuotes(s string) string { - return quoteEscaper.Replace(s) -} - -// FileAttachment writes the specified file into the body stream in an efficient way -// On the client side, the file will typically be downloaded with the given filename -func (c *Context) FileAttachment(filepath, filename string) { - if isASCII(filename) { - c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+escapeQuotes(filename)+`"`) - } else { - c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename)) - } - http.ServeFile(c.Writer, c.Request, filepath) -} - -// SSEvent writes a Server-Sent Event into the body stream. -func (c *Context) SSEvent(name string, message any) { - c.Render(-1, sse.Event{ - Event: name, - Data: message, - }) -} - -// Stream sends a streaming response and returns a boolean -// indicates "Is client disconnected in middle of stream" -func (c *Context) Stream(step func(w io.Writer) bool) bool { - w := c.Writer - clientGone := w.CloseNotify() - for { - select { - case <-clientGone: - return true - default: - keepOpen := step(w) - w.Flush() - if !keepOpen { - return false - } - } - } -} - -/************************************/ -/******** CONTENT NEGOTIATION *******/ -/************************************/ - -// Negotiate contains all negotiations data. -type Negotiate struct { - Offered []string - HTMLName string - HTMLData any - JSONData any - XMLData any - YAMLData any - Data any - TOMLData any -} - -// Negotiate calls different Render according to acceptable Accept format. -func (c *Context) Negotiate(code int, config Negotiate) { - switch c.NegotiateFormat(config.Offered...) { - case binding.MIMEJSON: - data := chooseData(config.JSONData, config.Data) - c.JSON(code, data) - - case binding.MIMEHTML: - data := chooseData(config.HTMLData, config.Data) - c.HTML(code, config.HTMLName, data) - - case binding.MIMEXML: - data := chooseData(config.XMLData, config.Data) - c.XML(code, data) - - case binding.MIMEYAML: - data := chooseData(config.YAMLData, config.Data) - c.YAML(code, data) - - case binding.MIMETOML: - data := chooseData(config.TOMLData, config.Data) - c.TOML(code, data) - - default: - c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) //nolint: errcheck - } -} - -// NegotiateFormat returns an acceptable Accept format. -func (c *Context) NegotiateFormat(offered ...string) string { - assert1(len(offered) > 0, "you must provide at least one offer") - - if c.Accepted == nil { - c.Accepted = parseAccept(c.requestHeader("Accept")) - } - if len(c.Accepted) == 0 { - return offered[0] - } - for _, accepted := range c.Accepted { - for _, offer := range offered { - // According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers, - // therefore we can just iterate over the string without casting it into []rune - i := 0 - for ; i < len(accepted) && i < len(offer); i++ { - if accepted[i] == '*' || offer[i] == '*' { - return offer - } - if accepted[i] != offer[i] { - break - } - } - if i == len(accepted) { - return offer - } - } - } - return "" -} - -// SetAccepted sets Accept header data. -func (c *Context) SetAccepted(formats ...string) { - c.Accepted = formats -} - -/************************************/ -/***** GOLANG.ORG/X/NET/CONTEXT *****/ -/************************************/ - -// hasRequestContext returns whether c.Request has Context and fallback. -func (c *Context) hasRequestContext() bool { - hasFallback := c.engine != nil && c.engine.ContextWithFallback - hasRequestContext := c.Request != nil && c.Request.Context() != nil - return hasFallback && hasRequestContext -} - -// Deadline returns that there is no deadline (ok==false) when c.Request has no Context. -func (c *Context) Deadline() (deadline time.Time, ok bool) { - if !c.hasRequestContext() { - return - } - return c.Request.Context().Deadline() -} - -// Done returns nil (chan which will wait forever) when c.Request has no Context. -func (c *Context) Done() <-chan struct{} { - if !c.hasRequestContext() { - return nil - } - return c.Request.Context().Done() -} - -// Err returns nil when c.Request has no Context. -func (c *Context) Err() error { - if !c.hasRequestContext() { - return nil - } - return c.Request.Context().Err() -} - -// Value returns the value associated with this context for key, or nil -// if no value is associated with key. Successive calls to Value with -// the same key returns the same result. -func (c *Context) Value(key any) any { - if key == ContextRequestKey { - return c.Request - } - if key == ContextKey { - return c - } - if keyAsString, ok := key.(string); ok { - if val, exists := c.Get(keyAsString); exists { - return val - } - } - if !c.hasRequestContext() { - return nil - } - return c.Request.Context().Value(key) -} |