diff --git a/context_test.go b/context_test.go
index 3b64ad4a..ecdc24d6 100644
--- a/context_test.go
+++ b/context_test.go
@@ -76,6 +76,285 @@ func must(err error) {
}
}
+// TestContextNegotiate tests the Context.Negotiate() method
+func TestContextNegotiate(t *testing.T) {
+ // Test JSON negotiation
+ t.Run("negotiate JSON", func(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, _ := CreateTestContext(w)
+ c.Request = httptest.NewRequest(http.MethodGet, "/test", nil)
+ c.Request.Header.Set("Accept", "application/json")
+
+ data := map[string]string{"message": "hello"}
+ c.Negotiate(http.StatusOK, Negotiate{
+ Offered: []string{binding.MIMEJSON, binding.MIMEHTML},
+ JSONData: data,
+ HTMLName: "test.html",
+ HTMLData: data,
+ })
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Header().Get("Content-Type"), "application/json")
+ assert.Contains(t, w.Body.String(), `"message":"hello"`)
+ })
+
+ // Test HTML negotiation
+ t.Run("negotiate HTML", func(t *testing.T) {
+ w := httptest.NewRecorder()
+ c, engine := CreateTestContext(w)
+ c.Request = httptest.NewRequest(http.MethodGet, "/test", nil)
+ c.Request.Header.Set("Accept", "text/html")
+
+ // Set up a simple HTML template
+ tmpl := template.Must(template.New("test").Parse(`
+
+
// 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 (
+ "crypto/subtle"
+ "encoding/base64"
+ "net/http"
+ "strconv"
+
+ "github.com/gin-gonic/gin/internal/bytesconv"
+)
+
+// AuthUserKey is the cookie name for user credential in basic auth.
+const AuthUserKey = "user"
+
+// AuthProxyUserKey is the cookie name for proxy_user credential in basic auth for proxy.
+const AuthProxyUserKey = "proxy_user"
+
+// Accounts defines a key/value for user/pass list of authorized logins.
+type Accounts map[string]string
+
+type authPair struct {
+ value string
+ user string
+}
+
+type authPairs []authPair
+
+func (a authPairs) searchCredential(authValue string) (string, bool) {
+ if authValue == "" {
+ return "", false
+ }
+ for _, pair := range a {
+ if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
+ return pair.user, true
+ }
+ }
+ return "", false
+}
+
+// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
+// the key is the user name and the value is the password, as well as the name of the Realm.
+// If the realm is empty, "Authorization Required" will be used by default.
+// (see http://tools.ietf.org/html/rfc2617#section-1.2)
+func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
+ if realm == "" {
+ realm = "Authorization Required"
+ }
+ realm = "Basic realm=" + strconv.Quote(realm)
+ pairs := processAccounts(accounts)
+ return func(c *Context) {
+ // Search user in the slice of allowed credentials
+ user, found := pairs.searchCredential(c.requestHeader("Authorization"))
+ if !found {
+ // Credentials doesn't match, we return 401 and abort handlers chain.
+ c.Header("WWW-Authenticate", realm)
+ c.AbortWithStatus(http.StatusUnauthorized)
+ return
+ }
+
+ // The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
+ // c.MustGet(gin.AuthUserKey).
+ c.Set(AuthUserKey, user)
+ }
+}
+
+// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
+// the key is the user name and the value is the password.
+func BasicAuth(accounts Accounts) HandlerFunc {
+ return BasicAuthForRealm(accounts, "")
+}
+
+func processAccounts(accounts Accounts) authPairs {
+ length := len(accounts)
+ assert1(length > 0, "Empty list of authorized credentials")
+ pairs := make(authPairs, 0, length)
+ for user, password := range accounts {
+ assert1(user != "", "User can not be empty")
+ value := authorizationHeader(user, password)
+ pairs = append(pairs, authPair{
+ value: value,
+ user: user,
+ })
+ }
+ return pairs
+}
+
+func authorizationHeader(user, password string) string {
+ base := user + ":" + password
+ return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
+}
+
+// BasicAuthForProxy returns a Basic HTTP Proxy-Authorization middleware.
+// If the realm is empty, "Proxy Authorization Required" will be used by default.
+func BasicAuthForProxy(accounts Accounts, realm string) HandlerFunc {
+ if realm == "" {
+ realm = "Proxy Authorization Required"
+ }
+ realm = "Basic realm=" + strconv.Quote(realm)
+ pairs := processAccounts(accounts)
+ return func(c *Context) {
+ proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization"))
+ if !found {
+ // Credentials doesn't match, we return 407 and abort handlers chain.
+ c.Header("Proxy-Authenticate", realm)
+ c.AbortWithStatus(http.StatusProxyAuthRequired)
+ return
+ }
+ // The proxy_user credentials was found, set proxy_user's id to key AuthProxyUserKey in this context, the proxy_user's id can be read later using
+ // c.MustGet(gin.AuthProxyUserKey).
+ c.Set(AuthProxyUserKey, proxyUser)
+ }
+}
+
+
+
// 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.
+
+//go:build !nomsgpack
+
+package binding
+
+import "net/http"
+
+// Content-Type MIME of the most common data formats.
+const (
+ MIMEJSON = "application/json"
+ MIMEHTML = "text/html"
+ MIMEXML = "application/xml"
+ MIMEXML2 = "text/xml"
+ MIMEPlain = "text/plain"
+ MIMEPOSTForm = "application/x-www-form-urlencoded"
+ MIMEMultipartPOSTForm = "multipart/form-data"
+ MIMEPROTOBUF = "application/x-protobuf"
+ MIMEMSGPACK = "application/x-msgpack"
+ MIMEMSGPACK2 = "application/msgpack"
+ MIMEYAML = "application/x-yaml"
+ MIMEYAML2 = "application/yaml"
+ MIMETOML = "application/toml"
+)
+
+// Binding describes the interface which needs to be implemented for binding the
+// data present in the request such as JSON request body, query parameters or
+// the form POST.
+type Binding interface {
+ Name() string
+ Bind(*http.Request, any) error
+}
+
+// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
+// but it reads the body from supplied bytes instead of req.Body.
+type BindingBody interface {
+ Binding
+ BindBody([]byte, any) error
+}
+
+// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
+// but it reads the Params.
+type BindingUri interface {
+ Name() string
+ BindUri(map[string][]string, any) error
+}
+
+// StructValidator is the minimal interface which needs to be implemented in
+// order for it to be used as the validator engine for ensuring the correctness
+// of the request. Gin provides a default implementation for this using
+// https://github.com/go-playground/validator/tree/v10.6.1.
+type StructValidator interface {
+ // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
+ // If the received type is a slice|array, the validation should be performed travel on every element.
+ // If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
+ // If the received type is a struct or pointer to a struct, the validation should be performed.
+ // If the struct is not valid or the validation itself fails, a descriptive error should be returned.
+ // Otherwise nil must be returned.
+ ValidateStruct(any) error
+
+ // Engine returns the underlying validator engine which powers the
+ // StructValidator implementation.
+ Engine() any
+}
+
+// Validator is the default validator which implements the StructValidator
+// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
+// under the hood.
+var Validator StructValidator = &defaultValidator{}
+
+// These implement the Binding interface and can be used to bind the data
+// present in the request to struct instances.
+var (
+ JSON BindingBody = jsonBinding{}
+ XML BindingBody = xmlBinding{}
+ Form Binding = formBinding{}
+ Query Binding = queryBinding{}
+ FormPost Binding = formPostBinding{}
+ FormMultipart Binding = formMultipartBinding{}
+ ProtoBuf BindingBody = protobufBinding{}
+ MsgPack BindingBody = msgpackBinding{}
+ YAML BindingBody = yamlBinding{}
+ Uri BindingUri = uriBinding{}
+ Header Binding = headerBinding{}
+ Plain BindingBody = plainBinding{}
+ TOML BindingBody = tomlBinding{}
+)
+
+// Default returns the appropriate Binding instance based on the HTTP method
+// and the content type.
+func Default(method, contentType string) Binding {
+ if method == http.MethodGet {
+ return Form
+ }
+
+ switch contentType {
+ case MIMEJSON:
+ return JSON
+ case MIMEXML, MIMEXML2:
+ return XML
+ case MIMEPROTOBUF:
+ return ProtoBuf
+ case MIMEMSGPACK, MIMEMSGPACK2:
+ return MsgPack
+ case MIMEYAML, MIMEYAML2:
+ return YAML
+ case MIMETOML:
+ return TOML
+ case MIMEMultipartPOSTForm:
+ return FormMultipart
+ default: // case MIMEPOSTForm:
+ return Form
+ }
+}
+
+func validate(obj any) error {
+ if Validator == nil {
+ return nil
+ }
+ return Validator.ValidateStruct(obj)
+}
+
+
+
// Copyright 2017 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 binding
+
+import (
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/go-playground/validator/v10"
+)
+
+type defaultValidator struct {
+ once sync.Once
+ validate *validator.Validate
+}
+
+type SliceValidationError []error
+
+// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
+func (err SliceValidationError) Error() string {
+ if len(err) == 0 {
+ return ""
+ }
+
+ var b strings.Builder
+ for i := 0; i < len(err); i++ {
+ if err[i] != nil {
+ if b.Len() > 0 {
+ b.WriteString("\n")
+ }
+ b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error())
+ }
+ }
+ return b.String()
+}
+
+var _ StructValidator = (*defaultValidator)(nil)
+
+// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
+func (v *defaultValidator) ValidateStruct(obj any) error {
+ if obj == nil {
+ return nil
+ }
+
+ value := reflect.ValueOf(obj)
+ switch value.Kind() {
+ case reflect.Ptr:
+ if value.Elem().Kind() != reflect.Struct {
+ return v.ValidateStruct(value.Elem().Interface())
+ }
+ return v.validateStruct(obj)
+ case reflect.Struct:
+ return v.validateStruct(obj)
+ case reflect.Slice, reflect.Array:
+ count := value.Len()
+ validateRet := make(SliceValidationError, 0)
+ for i := 0; i < count; i++ {
+ if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
+ validateRet = append(validateRet, err)
+ }
+ }
+ if len(validateRet) == 0 {
+ return nil
+ }
+ return validateRet
+ default:
+ return nil
+ }
+}
+
+// validateStruct receives struct type
+func (v *defaultValidator) validateStruct(obj any) error {
+ v.lazyinit()
+ return v.validate.Struct(obj)
+}
+
+// Engine returns the underlying validator engine which powers the default
+// Validator instance. This is useful if you want to register custom validations
+// or struct level validations. See validator GoDoc for more info -
+// https://pkg.go.dev/github.com/go-playground/validator/v10
+func (v *defaultValidator) Engine() any {
+ v.lazyinit()
+ return v.validate
+}
+
+func (v *defaultValidator) lazyinit() {
+ v.once.Do(func() {
+ v.validate = validator.New()
+ v.validate.SetTagName("binding")
+ })
+}
+
+
+
// 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 binding
+
+import (
+ "errors"
+ "net/http"
+)
+
+const defaultMemory = 32 << 20
+
+type (
+ formBinding struct{}
+ formPostBinding struct{}
+ formMultipartBinding struct{}
+)
+
+func (formBinding) Name() string {
+ return "form"
+}
+
+func (formBinding) Bind(req *http.Request, obj any) error {
+ if err := req.ParseForm(); err != nil {
+ return err
+ }
+ if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
+ return err
+ }
+ if err := mapForm(obj, req.Form); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+func (formPostBinding) Name() string {
+ return "form-urlencoded"
+}
+
+func (formPostBinding) Bind(req *http.Request, obj any) error {
+ if err := req.ParseForm(); err != nil {
+ return err
+ }
+ if err := mapForm(obj, req.PostForm); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+func (formMultipartBinding) Name() string {
+ return "multipart/form-data"
+}
+
+func (formMultipartBinding) Bind(req *http.Request, obj any) error {
+ if err := req.ParseMultipartForm(defaultMemory); err != nil {
+ return err
+ }
+ if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil {
+ return err
+ }
+
+ return validate(obj)
+}
+
+
+
// 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 binding
+
+import (
+ "errors"
+ "fmt"
+ "mime/multipart"
+ "reflect"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/gin-gonic/gin/codec/json"
+ "github.com/gin-gonic/gin/internal/bytesconv"
+)
+
+var (
+ errUnknownType = errors.New("unknown type")
+
+ // ErrConvertMapStringSlice can not convert to map[string][]string
+ ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")
+
+ // ErrConvertToMapString can not convert to map[string]string
+ ErrConvertToMapString = errors.New("can not convert to map of strings")
+)
+
+func mapURI(ptr any, m map[string][]string) error {
+ return mapFormByTag(ptr, m, "uri")
+}
+
+func mapForm(ptr any, form map[string][]string) error {
+ return mapFormByTag(ptr, form, "form")
+}
+
+func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
+ return mapFormByTag(ptr, form, tag)
+}
+
+var emptyField = reflect.StructField{}
+
+func mapFormByTag(ptr any, form map[string][]string, tag string) error {
+ // Check if ptr is a map
+ ptrVal := reflect.ValueOf(ptr)
+ var pointed any
+ if ptrVal.Kind() == reflect.Ptr {
+ ptrVal = ptrVal.Elem()
+ pointed = ptrVal.Interface()
+ }
+ if ptrVal.Kind() == reflect.Map &&
+ ptrVal.Type().Key().Kind() == reflect.String {
+ if pointed != nil {
+ ptr = pointed
+ }
+ return setFormMap(ptr, form)
+ }
+
+ return mappingByPtr(ptr, formSource(form), tag)
+}
+
+// setter tries to set value on a walking by fields of a struct
+type setter interface {
+ TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
+}
+
+type formSource map[string][]string
+
+var _ setter = formSource(nil)
+
+// TrySet tries to set a value by request's form source (like map[string][]string)
+func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
+ return setByForm(value, field, form, tagValue, opt)
+}
+
+func mappingByPtr(ptr any, setter setter, tag string) error {
+ _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
+ return err
+}
+
+func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+ if field.Tag.Get(tag) == "-" { // just ignoring this field
+ return false, nil
+ }
+
+ vKind := value.Kind()
+
+ if vKind == reflect.Ptr {
+ var isNew bool
+ vPtr := value
+ if value.IsNil() {
+ isNew = true
+ vPtr = reflect.New(value.Type().Elem())
+ }
+ isSet, err := mapping(vPtr.Elem(), field, setter, tag)
+ if err != nil {
+ return false, err
+ }
+ if isNew && isSet {
+ value.Set(vPtr)
+ }
+ return isSet, nil
+ }
+
+ if vKind != reflect.Struct || !field.Anonymous {
+ ok, err := tryToSetValue(value, field, setter, tag)
+ if err != nil {
+ return false, err
+ }
+ if ok {
+ return true, nil
+ }
+ }
+
+ if vKind == reflect.Struct {
+ tValue := value.Type()
+
+ var isSet bool
+ for i := 0; i < value.NumField(); i++ {
+ sf := tValue.Field(i)
+ if sf.PkgPath != "" && !sf.Anonymous { // unexported
+ continue
+ }
+ ok, err := mapping(value.Field(i), sf, setter, tag)
+ if err != nil {
+ return false, err
+ }
+ isSet = isSet || ok
+ }
+ return isSet, nil
+ }
+ return false, nil
+}
+
+type setOptions struct {
+ isDefaultExists bool
+ defaultValue string
+}
+
+func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+ var tagValue string
+ var setOpt setOptions
+
+ tagValue = field.Tag.Get(tag)
+ tagValue, opts := head(tagValue, ",")
+
+ if tagValue == "" { // default value is FieldName
+ tagValue = field.Name
+ }
+ if tagValue == "" { // when field is "emptyField" variable
+ return false, nil
+ }
+
+ var opt string
+ for len(opts) > 0 {
+ opt, opts = head(opts, ",")
+
+ if k, v := head(opt, "="); k == "default" {
+ setOpt.isDefaultExists = true
+ setOpt.defaultValue = v
+
+ // convert semicolon-separated default values to csv-separated values for processing in setByForm
+ if field.Type.Kind() == reflect.Slice || field.Type.Kind() == reflect.Array {
+ cfTag := field.Tag.Get("collection_format")
+ if cfTag == "" || cfTag == "multi" || cfTag == "csv" {
+ setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
+ }
+ }
+ }
+ }
+
+ return setter.TrySet(value, field, tagValue, setOpt)
+}
+
+// BindUnmarshaler is the interface used to wrap the UnmarshalParam method.
+type BindUnmarshaler interface {
+ // UnmarshalParam decodes and assigns a value from an form or query param.
+ UnmarshalParam(param string) error
+}
+
+// trySetCustom tries to set a custom type value
+// If the value implements the BindUnmarshaler interface, it will be used to set the value, we will return `true`
+// to skip the default value setting.
+func trySetCustom(val string, value reflect.Value) (isSet bool, err error) {
+ switch v := value.Addr().Interface().(type) {
+ case BindUnmarshaler:
+ return true, v.UnmarshalParam(val)
+ }
+ return false, nil
+}
+
+func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) {
+ cfTag := field.Tag.Get("collection_format")
+ if cfTag == "" || cfTag == "multi" {
+ return vs, nil
+ }
+
+ var sep string
+ switch cfTag {
+ case "csv":
+ sep = ","
+ case "ssv":
+ sep = " "
+ case "tsv":
+ sep = "\t"
+ case "pipes":
+ sep = "|"
+ default:
+ return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag)
+ }
+
+ totalLength := 0
+ for _, v := range vs {
+ totalLength += strings.Count(v, sep) + 1
+ }
+ newVs = make([]string, 0, totalLength)
+ for _, v := range vs {
+ newVs = append(newVs, strings.Split(v, sep)...)
+ }
+
+ return newVs, nil
+}
+
+func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
+ vs, ok := form[tagValue]
+ if !ok && !opt.isDefaultExists {
+ return false, nil
+ }
+
+ switch value.Kind() {
+ case reflect.Slice:
+ if !ok {
+ vs = []string{opt.defaultValue}
+
+ // pre-process the default value for multi if present
+ cfTag := field.Tag.Get("collection_format")
+ if cfTag == "" || cfTag == "multi" {
+ vs = strings.Split(opt.defaultValue, ",")
+ }
+ }
+
+ if ok, err = trySetCustom(vs[0], value); ok {
+ return ok, err
+ }
+
+ if vs, err = trySplit(vs, field); err != nil {
+ return false, err
+ }
+
+ return true, setSlice(vs, value, field)
+ case reflect.Array:
+ if !ok {
+ vs = []string{opt.defaultValue}
+
+ // pre-process the default value for multi if present
+ cfTag := field.Tag.Get("collection_format")
+ if cfTag == "" || cfTag == "multi" {
+ vs = strings.Split(opt.defaultValue, ",")
+ }
+ }
+
+ if ok, err = trySetCustom(vs[0], value); ok {
+ return ok, err
+ }
+
+ if vs, err = trySplit(vs, field); err != nil {
+ return false, err
+ }
+
+ if len(vs) != value.Len() {
+ return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
+ }
+
+ return true, setArray(vs, value, field)
+ default:
+ var val string
+ if !ok {
+ val = opt.defaultValue
+ }
+
+ if len(vs) > 0 {
+ val = vs[0]
+ if val == "" {
+ val = opt.defaultValue
+ }
+ }
+ if ok, err := trySetCustom(val, value); ok {
+ return ok, err
+ }
+ return true, setWithProperType(val, value, field)
+ }
+}
+
+func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
+ switch value.Kind() {
+ case reflect.Int:
+ return setIntField(val, 0, value)
+ case reflect.Int8:
+ return setIntField(val, 8, value)
+ case reflect.Int16:
+ return setIntField(val, 16, value)
+ case reflect.Int32:
+ return setIntField(val, 32, value)
+ case reflect.Int64:
+ switch value.Interface().(type) {
+ case time.Duration:
+ return setTimeDuration(val, value)
+ }
+ return setIntField(val, 64, value)
+ case reflect.Uint:
+ return setUintField(val, 0, value)
+ case reflect.Uint8:
+ return setUintField(val, 8, value)
+ case reflect.Uint16:
+ return setUintField(val, 16, value)
+ case reflect.Uint32:
+ return setUintField(val, 32, value)
+ case reflect.Uint64:
+ return setUintField(val, 64, value)
+ case reflect.Bool:
+ return setBoolField(val, value)
+ case reflect.Float32:
+ return setFloatField(val, 32, value)
+ case reflect.Float64:
+ return setFloatField(val, 64, value)
+ case reflect.String:
+ value.SetString(val)
+ case reflect.Struct:
+ switch value.Interface().(type) {
+ case time.Time:
+ return setTimeField(val, field, value)
+ case multipart.FileHeader:
+ return nil
+ }
+ return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
+ case reflect.Map:
+ return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
+ case reflect.Ptr:
+ if !value.Elem().IsValid() {
+ value.Set(reflect.New(value.Type().Elem()))
+ }
+ return setWithProperType(val, value.Elem(), field)
+ default:
+ return errUnknownType
+ }
+ return nil
+}
+
+func setIntField(val string, bitSize int, field reflect.Value) error {
+ if val == "" {
+ val = "0"
+ }
+ intVal, err := strconv.ParseInt(val, 10, bitSize)
+ if err == nil {
+ field.SetInt(intVal)
+ }
+ return err
+}
+
+func setUintField(val string, bitSize int, field reflect.Value) error {
+ if val == "" {
+ val = "0"
+ }
+ uintVal, err := strconv.ParseUint(val, 10, bitSize)
+ if err == nil {
+ field.SetUint(uintVal)
+ }
+ return err
+}
+
+func setBoolField(val string, field reflect.Value) error {
+ if val == "" {
+ val = "false"
+ }
+ boolVal, err := strconv.ParseBool(val)
+ if err == nil {
+ field.SetBool(boolVal)
+ }
+ return err
+}
+
+func setFloatField(val string, bitSize int, field reflect.Value) error {
+ if val == "" {
+ val = "0.0"
+ }
+ floatVal, err := strconv.ParseFloat(val, bitSize)
+ if err == nil {
+ field.SetFloat(floatVal)
+ }
+ return err
+}
+
+func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
+ timeFormat := structField.Tag.Get("time_format")
+ if timeFormat == "" {
+ timeFormat = time.RFC3339
+ }
+
+ switch tf := strings.ToLower(timeFormat); tf {
+ case "unix", "unixmilli", "unixmicro", "unixnano":
+ tv, err := strconv.ParseInt(val, 10, 64)
+ if err != nil {
+ return err
+ }
+
+ var t time.Time
+ switch tf {
+ case "unix":
+ t = time.Unix(tv, 0)
+ case "unixmilli":
+ t = time.UnixMilli(tv)
+ case "unixmicro":
+ t = time.UnixMicro(tv)
+ default:
+ t = time.Unix(0, tv)
+ }
+
+ value.Set(reflect.ValueOf(t))
+ return nil
+ }
+
+ if val == "" {
+ value.Set(reflect.ValueOf(time.Time{}))
+ return nil
+ }
+
+ l := time.Local
+ if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
+ l = time.UTC
+ }
+
+ if locTag := structField.Tag.Get("time_location"); locTag != "" {
+ loc, err := time.LoadLocation(locTag)
+ if err != nil {
+ return err
+ }
+ l = loc
+ }
+
+ t, err := time.ParseInLocation(timeFormat, val, l)
+ if err != nil {
+ return err
+ }
+
+ value.Set(reflect.ValueOf(t))
+ return nil
+}
+
+func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
+ for i, s := range vals {
+ err := setWithProperType(s, value.Index(i), field)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
+ slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
+ err := setArray(vals, slice, field)
+ if err != nil {
+ return err
+ }
+ value.Set(slice)
+ return nil
+}
+
+func setTimeDuration(val string, value reflect.Value) error {
+ d, err := time.ParseDuration(val)
+ if err != nil {
+ return err
+ }
+ value.Set(reflect.ValueOf(d))
+ return nil
+}
+
+func head(str, sep string) (head string, tail string) {
+ head, tail, _ = strings.Cut(str, sep)
+ return head, tail
+}
+
+func setFormMap(ptr any, form map[string][]string) error {
+ el := reflect.TypeOf(ptr).Elem()
+
+ if el.Kind() == reflect.Slice {
+ ptrMap, ok := ptr.(map[string][]string)
+ if !ok {
+ return ErrConvertMapStringSlice
+ }
+ for k, v := range form {
+ ptrMap[k] = v
+ }
+
+ return nil
+ }
+
+ ptrMap, ok := ptr.(map[string]string)
+ if !ok {
+ return ErrConvertToMapString
+ }
+ for k, v := range form {
+ ptrMap[k] = v[len(v)-1] // pick last
+ }
+
+ return nil
+}
+
+
+
// Copyright 2022 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+ "net/http"
+ "net/textproto"
+ "reflect"
+)
+
+type headerBinding struct{}
+
+func (headerBinding) Name() string {
+ return "header"
+}
+
+func (headerBinding) Bind(req *http.Request, obj any) error {
+ if err := mapHeader(obj, req.Header); err != nil {
+ return err
+ }
+
+ return validate(obj)
+}
+
+func mapHeader(ptr any, h map[string][]string) error {
+ return mappingByPtr(ptr, headerSource(h), "header")
+}
+
+type headerSource map[string][]string
+
+var _ setter = headerSource(nil)
+
+func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) {
+ return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
+}
+
+
+
// 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 binding
+
+import (
+ "bytes"
+ "errors"
+ "io"
+ "net/http"
+
+ "github.com/gin-gonic/gin/codec/json"
+)
+
+// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
+// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
+// any as a Number instead of as a float64.
+var EnableDecoderUseNumber = false
+
+// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
+// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to
+// return an error when the destination is a struct and the input contains object
+// keys which do not match any non-ignored, exported fields in the destination.
+var EnableDecoderDisallowUnknownFields = false
+
+type jsonBinding struct{}
+
+func (jsonBinding) Name() string {
+ return "json"
+}
+
+func (jsonBinding) Bind(req *http.Request, obj any) error {
+ if req == nil || req.Body == nil {
+ return errors.New("invalid request")
+ }
+ return decodeJSON(req.Body, obj)
+}
+
+func (jsonBinding) BindBody(body []byte, obj any) error {
+ return decodeJSON(bytes.NewReader(body), obj)
+}
+
+func decodeJSON(r io.Reader, obj any) error {
+ decoder := json.API.NewDecoder(r)
+ if EnableDecoderUseNumber {
+ decoder.UseNumber()
+ }
+ if EnableDecoderDisallowUnknownFields {
+ decoder.DisallowUnknownFields()
+ }
+ if err := decoder.Decode(obj); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// Copyright 2017 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.
+
+//go:build !nomsgpack
+
+package binding
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+
+ "github.com/ugorji/go/codec"
+)
+
+type msgpackBinding struct{}
+
+func (msgpackBinding) Name() string {
+ return "msgpack"
+}
+
+func (msgpackBinding) Bind(req *http.Request, obj any) error {
+ return decodeMsgPack(req.Body, obj)
+}
+
+func (msgpackBinding) BindBody(body []byte, obj any) error {
+ return decodeMsgPack(bytes.NewReader(body), obj)
+}
+
+func decodeMsgPack(r io.Reader, obj any) error {
+ cdc := new(codec.MsgpackHandle)
+ if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// Copyright 2019 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+ "errors"
+ "mime/multipart"
+ "net/http"
+ "reflect"
+)
+
+type multipartRequest http.Request
+
+var _ setter = (*multipartRequest)(nil)
+
+var (
+ // ErrMultiFileHeader multipart.FileHeader invalid
+ ErrMultiFileHeader = errors.New("unsupported field type for multipart.FileHeader")
+
+ // ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid
+ ErrMultiFileHeaderLenInvalid = errors.New("unsupported len of array for []*multipart.FileHeader")
+)
+
+// TrySet tries to set a value by the multipart request with the binding a form file
+func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) {
+ if files := r.MultipartForm.File[key]; len(files) != 0 {
+ return setByMultipartFormFile(value, field, files)
+ }
+
+ return setByForm(value, field, r.MultipartForm.Value, key, opt)
+}
+
+func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
+ switch value.Kind() {
+ case reflect.Ptr:
+ switch value.Interface().(type) {
+ case *multipart.FileHeader:
+ value.Set(reflect.ValueOf(files[0]))
+ return true, nil
+ }
+ case reflect.Struct:
+ switch value.Interface().(type) {
+ case multipart.FileHeader:
+ value.Set(reflect.ValueOf(*files[0]))
+ return true, nil
+ }
+ case reflect.Slice:
+ slice := reflect.MakeSlice(value.Type(), len(files), len(files))
+ isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
+ if err != nil || !isSet {
+ return isSet, err
+ }
+ value.Set(slice)
+ return true, nil
+ case reflect.Array:
+ return setArrayOfMultipartFormFiles(value, field, files)
+ }
+ return false, ErrMultiFileHeader
+}
+
+func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
+ if value.Len() != len(files) {
+ return false, ErrMultiFileHeaderLenInvalid
+ }
+ for i := range files {
+ set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
+ if err != nil || !set {
+ return set, err
+ }
+ }
+ return true, nil
+}
+
+
+
package binding
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "reflect"
+
+ "github.com/gin-gonic/gin/internal/bytesconv"
+)
+
+type plainBinding struct{}
+
+func (plainBinding) Name() string {
+ return "plain"
+}
+
+func (plainBinding) Bind(req *http.Request, obj any) error {
+ all, err := io.ReadAll(req.Body)
+ if err != nil {
+ return err
+ }
+
+ return decodePlain(all, obj)
+}
+
+func (plainBinding) BindBody(body []byte, obj any) error {
+ return decodePlain(body, obj)
+}
+
+func decodePlain(data []byte, obj any) error {
+ if obj == nil {
+ return nil
+ }
+
+ v := reflect.ValueOf(obj)
+
+ for v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return nil
+ }
+ v = v.Elem()
+ }
+
+ if v.Kind() == reflect.String {
+ v.SetString(bytesconv.BytesToString(data))
+ return nil
+ }
+
+ if _, ok := v.Interface().([]byte); ok {
+ v.SetBytes(data)
+ return nil
+ }
+
+ return fmt.Errorf("type (%T) unknown type", v)
+}
+
+
+
// 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 binding
+
+import (
+ "errors"
+ "io"
+ "net/http"
+
+ "google.golang.org/protobuf/proto"
+)
+
+type protobufBinding struct{}
+
+func (protobufBinding) Name() string {
+ return "protobuf"
+}
+
+func (b protobufBinding) Bind(req *http.Request, obj any) error {
+ buf, err := io.ReadAll(req.Body)
+ if err != nil {
+ return err
+ }
+ return b.BindBody(buf, obj)
+}
+
+func (protobufBinding) BindBody(body []byte, obj any) error {
+ msg, ok := obj.(proto.Message)
+ if !ok {
+ return errors.New("obj is not ProtoMessage")
+ }
+ if err := proto.Unmarshal(body, msg); err != nil {
+ return err
+ }
+ // Here it's same to return validate(obj), but until now we can't add
+ // `binding:""` to the struct which automatically generate by gen-proto
+ return nil
+ // return validate(obj)
+}
+
+
+
// Copyright 2017 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 binding
+
+import "net/http"
+
+type queryBinding struct{}
+
+func (queryBinding) Name() string {
+ return "query"
+}
+
+func (queryBinding) Bind(req *http.Request, obj any) error {
+ values := req.URL.Query()
+ if err := mapForm(obj, values); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// Copyright 2022 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+
+ "github.com/pelletier/go-toml/v2"
+)
+
+type tomlBinding struct{}
+
+func (tomlBinding) Name() string {
+ return "toml"
+}
+
+func (tomlBinding) Bind(req *http.Request, obj any) error {
+ return decodeToml(req.Body, obj)
+}
+
+func (tomlBinding) BindBody(body []byte, obj any) error {
+ return decodeToml(bytes.NewReader(body), obj)
+}
+
+func decodeToml(r io.Reader, obj any) error {
+ decoder := toml.NewDecoder(r)
+ if err := decoder.Decode(obj); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// Copyright 2018 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+type uriBinding struct{}
+
+func (uriBinding) Name() string {
+ return "uri"
+}
+
+func (uriBinding) BindUri(m map[string][]string, obj any) error {
+ if err := mapURI(obj, m); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// 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 binding
+
+import (
+ "bytes"
+ "encoding/xml"
+ "io"
+ "net/http"
+)
+
+type xmlBinding struct{}
+
+func (xmlBinding) Name() string {
+ return "xml"
+}
+
+func (xmlBinding) Bind(req *http.Request, obj any) error {
+ return decodeXML(req.Body, obj)
+}
+
+func (xmlBinding) BindBody(body []byte, obj any) error {
+ return decodeXML(bytes.NewReader(body), obj)
+}
+
+func decodeXML(r io.Reader, obj any) error {
+ decoder := xml.NewDecoder(r)
+ if err := decoder.Decode(obj); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// Copyright 2018 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package binding
+
+import (
+ "bytes"
+ "io"
+ "net/http"
+
+ "github.com/goccy/go-yaml"
+)
+
+type yamlBinding struct{}
+
+func (yamlBinding) Name() string {
+ return "yaml"
+}
+
+func (yamlBinding) Bind(req *http.Request, obj any) error {
+ return decodeYAML(req.Body, obj)
+}
+
+func (yamlBinding) BindBody(body []byte, obj any) error {
+ return decodeYAML(bytes.NewReader(body), obj)
+}
+
+func decodeYAML(r io.Reader, obj any) error {
+ decoder := yaml.NewDecoder(r)
+ if err := decoder.Decode(obj); err != nil {
+ return err
+ }
+ return validate(obj)
+}
+
+
+
// Copyright 2025 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+//go:build !jsoniter && !go_json && !(sonic && (linux || windows || darwin))
+
+package json
+
+import (
+ "encoding/json"
+ "io"
+)
+
+// Package indicates what library is being used for JSON encoding.
+const Package = "encoding/json"
+
+func init() {
+ API = jsonApi{}
+}
+
+type jsonApi struct{}
+
+func (j jsonApi) Marshal(v any) ([]byte, error) {
+ return json.Marshal(v)
+}
+
+func (j jsonApi) Unmarshal(data []byte, v any) error {
+ return json.Unmarshal(data, v)
+}
+
+func (j jsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
+ return json.MarshalIndent(v, prefix, indent)
+}
+
+func (j jsonApi) NewEncoder(writer io.Writer) Encoder {
+ return json.NewEncoder(writer)
+}
+
+func (j jsonApi) NewDecoder(reader io.Reader) Decoder {
+ return json.NewDecoder(reader)
+}
+
+
+
// 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"
+ "fmt"
+ "io"
+ "io/fs"
+ "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
+ MIMEYAML2 = binding.MIMEYAML2
+ 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[any]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[any]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 {
+ if val == nil {
+ continue
+ }
+ 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)) {
+ if c.handlers[c.index] != nil {
+ 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 `PureJSON` internally.
+// This method stops the chain, writes the status code and return a JSON body without escaping.
+// It also sets the Content-Type as "application/json".
+func (c *Context) AbortWithStatusPureJSON(code int, jsonObj any) {
+ c.Abort()
+ c.PureJSON(code, jsonObj)
+}
+
+// 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 any, value any) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if c.Keys == nil {
+ c.Keys = make(map[any]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 any) (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 any) any {
+ if value, exists := c.Get(key); exists {
+ return value
+ }
+ panic(fmt.Sprintf("key %v does not exist", key))
+}
+
+func getTyped[T any](c *Context, key any) (res T) {
+ if val, ok := c.Get(key); ok && val != nil {
+ res, _ = val.(T)
+ }
+ return
+}
+
+// GetString returns the value associated with the key as a string.
+func (c *Context) GetString(key any) (s string) {
+ return getTyped[string](c, key)
+}
+
+// GetBool returns the value associated with the key as a boolean.
+func (c *Context) GetBool(key any) (b bool) {
+ return getTyped[bool](c, key)
+}
+
+// GetInt returns the value associated with the key as an integer.
+func (c *Context) GetInt(key any) (i int) {
+ return getTyped[int](c, key)
+}
+
+// GetInt8 returns the value associated with the key as an integer 8.
+func (c *Context) GetInt8(key any) (i8 int8) {
+ return getTyped[int8](c, key)
+}
+
+// GetInt16 returns the value associated with the key as an integer 16.
+func (c *Context) GetInt16(key any) (i16 int16) {
+ return getTyped[int16](c, key)
+}
+
+// GetInt32 returns the value associated with the key as an integer 32.
+func (c *Context) GetInt32(key any) (i32 int32) {
+ return getTyped[int32](c, key)
+}
+
+// GetInt64 returns the value associated with the key as an integer 64.
+func (c *Context) GetInt64(key any) (i64 int64) {
+ return getTyped[int64](c, key)
+}
+
+// GetUint returns the value associated with the key as an unsigned integer.
+func (c *Context) GetUint(key any) (ui uint) {
+ return getTyped[uint](c, key)
+}
+
+// GetUint8 returns the value associated with the key as an unsigned integer 8.
+func (c *Context) GetUint8(key any) (ui8 uint8) {
+ return getTyped[uint8](c, key)
+}
+
+// GetUint16 returns the value associated with the key as an unsigned integer 16.
+func (c *Context) GetUint16(key any) (ui16 uint16) {
+ return getTyped[uint16](c, key)
+}
+
+// GetUint32 returns the value associated with the key as an unsigned integer 32.
+func (c *Context) GetUint32(key any) (ui32 uint32) {
+ return getTyped[uint32](c, key)
+}
+
+// GetUint64 returns the value associated with the key as an unsigned integer 64.
+func (c *Context) GetUint64(key any) (ui64 uint64) {
+ return getTyped[uint64](c, key)
+}
+
+// GetFloat32 returns the value associated with the key as a float32.
+func (c *Context) GetFloat32(key any) (f32 float32) {
+ return getTyped[float32](c, key)
+}
+
+// GetFloat64 returns the value associated with the key as a float64.
+func (c *Context) GetFloat64(key any) (f64 float64) {
+ return getTyped[float64](c, key)
+}
+
+// GetTime returns the value associated with the key as time.
+func (c *Context) GetTime(key any) (t time.Time) {
+ return getTyped[time.Time](c, key)
+}
+
+// GetDuration returns the value associated with the key as a duration.
+func (c *Context) GetDuration(key any) (d time.Duration) {
+ return getTyped[time.Duration](c, key)
+}
+
+// GetIntSlice returns the value associated with the key as a slice of integers.
+func (c *Context) GetIntSlice(key any) (is []int) {
+ return getTyped[[]int](c, key)
+}
+
+// GetInt8Slice returns the value associated with the key as a slice of int8 integers.
+func (c *Context) GetInt8Slice(key any) (i8s []int8) {
+ return getTyped[[]int8](c, key)
+}
+
+// GetInt16Slice returns the value associated with the key as a slice of int16 integers.
+func (c *Context) GetInt16Slice(key any) (i16s []int16) {
+ return getTyped[[]int16](c, key)
+}
+
+// GetInt32Slice returns the value associated with the key as a slice of int32 integers.
+func (c *Context) GetInt32Slice(key any) (i32s []int32) {
+ return getTyped[[]int32](c, key)
+}
+
+// GetInt64Slice returns the value associated with the key as a slice of int64 integers.
+func (c *Context) GetInt64Slice(key any) (i64s []int64) {
+ return getTyped[[]int64](c, key)
+}
+
+// GetUintSlice returns the value associated with the key as a slice of unsigned integers.
+func (c *Context) GetUintSlice(key any) (uis []uint) {
+ return getTyped[[]uint](c, key)
+}
+
+// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.
+func (c *Context) GetUint8Slice(key any) (ui8s []uint8) {
+ return getTyped[[]uint8](c, key)
+}
+
+// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.
+func (c *Context) GetUint16Slice(key any) (ui16s []uint16) {
+ return getTyped[[]uint16](c, key)
+}
+
+// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.
+func (c *Context) GetUint32Slice(key any) (ui32s []uint32) {
+ return getTyped[[]uint32](c, key)
+}
+
+// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.
+func (c *Context) GetUint64Slice(key any) (ui64s []uint64) {
+ return getTyped[[]uint64](c, key)
+}
+
+// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.
+func (c *Context) GetFloat32Slice(key any) (f32s []float32) {
+ return getTyped[[]float32](c, key)
+}
+
+// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.
+func (c *Context) GetFloat64Slice(key any) (f64s []float64) {
+ return getTyped[[]float64](c, key)
+}
+
+// GetStringSlice returns the value associated with the key as a slice of strings.
+func (c *Context) GetStringSlice(key any) (ss []string) {
+ return getTyped[[]string](c, key)
+}
+
+// GetStringMap returns the value associated with the key as a map of interfaces.
+func (c *Context) GetStringMap(key any) (sm map[string]any) {
+ return getTyped[map[string]any](c, key)
+}
+
+// GetStringMapString returns the value associated with the key as a map of strings.
+func (c *Context) GetStringMapString(key any) (sms map[string]string) {
+ return getTyped[map[string]string](c, key)
+}
+
+// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.
+func (c *Context) GetStringMapStringSlice(key any) (smss map[string][]string) {
+ return getTyped[map[string][]string](c, key)
+}
+
+/************************************/
+/************ 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.Request.URL != 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, perm ...fs.FileMode) error {
+ src, err := file.Open()
+ if err != nil {
+ return err
+ }
+ defer src.Close()
+
+ var mode os.FileMode = 0o750
+ if len(perm) > 0 {
+ mode = perm[0]
+ }
+ dir := filepath.Dir(dst)
+ if err = os.MkdirAll(dir, mode); err != nil {
+ return err
+ }
+ if err = os.Chmod(dir, mode); 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)
+}
+
+// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain).
+func (c *Context) BindPlain(obj any) error {
+ return c.MustBindWith(obj, binding.Plain)
+}
+
+// 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 {
+ err := c.ShouldBindWith(obj, b)
+ if err != nil {
+ var maxBytesErr *http.MaxBytesError
+
+ // Note: When using sonic or go-json as JSON encoder, they do not propagate the http.MaxBytesError error
+ // https://github.com/goccy/go-json/issues/485
+ // https://github.com/bytedance/sonic/issues/800
+ switch {
+ case errors.As(err, &maxBytesErr):
+ c.AbortWithError(http.StatusRequestEntityTooLarge, err).SetType(ErrorTypeBind) //nolint: errcheck
+ default:
+ 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)
+}
+
+// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).
+func (c *Context) ShouldBindPlain(obj any) error {
+ return c.ShouldBindWith(obj, binding.Plain)
+}
+
+// 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)
+}
+
+// ShouldBindBodyWithPlain is a shortcut for c.ShouldBindBodyWith(obj, binding.Plain).
+func (c *Context) ShouldBindBodyWithPlain(obj any) error {
+ return c.ShouldBindBodyWith(obj, binding.Plain)
+}
+
+// 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,
+ })
+}
+
+// SetCookieData adds a Set-Cookie header to the ResponseWriter's headers.
+// It accepts a pointer to http.Cookie structure for more flexibility in setting cookie attributes.
+// The provided cookie must have a valid Name. Invalid cookies may be silently dropped.
+func (c *Context) SetCookieData(cookie *http.Cookie) {
+ if cookie.Path == "" {
+ cookie.Path = "/"
+ }
+ if cookie.SameSite == http.SameSiteDefaultMode {
+ cookie.SameSite = c.sameSite
+ }
+ http.SetCookie(c.Writer, cookie)
+}
+
+// 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, binding.MIMEYAML2:
+ 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)
+}
+
+
+
// 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 (
+ "fmt"
+ "html/template"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync/atomic"
+)
+
+const ginSupportMinGoVer = 23
+
+// IsDebugging returns true if the framework is running in debug mode.
+// Use SetMode(gin.ReleaseMode) to disable debug mode.
+func IsDebugging() bool {
+ return atomic.LoadInt32(&ginMode) == debugCode
+}
+
+// DebugPrintRouteFunc indicates debug log output format.
+var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
+
+// DebugPrintFunc indicates debug log output format.
+var DebugPrintFunc func(format string, values ...any)
+
+func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
+ if IsDebugging() {
+ nuHandlers := len(handlers)
+ handlerName := nameOfFunction(handlers.Last())
+ if DebugPrintRouteFunc == nil {
+ debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
+ } else {
+ DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers)
+ }
+ }
+}
+
+func debugPrintLoadTemplate(tmpl *template.Template) {
+ if IsDebugging() {
+ var buf strings.Builder
+ for _, tmpl := range tmpl.Templates() {
+ buf.WriteString("\t- ")
+ buf.WriteString(tmpl.Name())
+ buf.WriteString("\n")
+ }
+ debugPrint("Loaded HTML Templates (%d): \n%s\n", len(tmpl.Templates()), buf.String())
+ }
+}
+
+func debugPrint(format string, values ...any) {
+ if !IsDebugging() {
+ return
+ }
+
+ if DebugPrintFunc != nil {
+ DebugPrintFunc(format, values...)
+ return
+ }
+
+ if !strings.HasSuffix(format, "\n") {
+ format += "\n"
+ }
+ fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)
+}
+
+func getMinVer(v string) (uint64, error) {
+ first := strings.IndexByte(v, '.')
+ last := strings.LastIndexByte(v, '.')
+ if first == last {
+ return strconv.ParseUint(v[first+1:], 10, 64)
+ }
+ return strconv.ParseUint(v[first+1:last], 10, 64)
+}
+
+func debugPrintWARNINGDefault() {
+ if v, e := getMinVer(runtime.Version()); e == nil && v < ginSupportMinGoVer {
+ debugPrint(`[WARNING] Now Gin requires Go 1.23+.
+
+`)
+ }
+ debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
+
+`)
+}
+
+func debugPrintWARNINGNew() {
+ debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
+ - using env: export GIN_MODE=release
+ - using code: gin.SetMode(gin.ReleaseMode)
+
+`)
+}
+
+func debugPrintWARNINGSetHTMLTemplate() {
+ debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
+at initialization. ie. before any route is registered or the router is listening in a socket:
+
+ router := gin.Default()
+ router.SetHTMLTemplate(template) // << good place
+
+`)
+}
+
+func debugPrintError(err error) {
+ if err != nil && IsDebugging() {
+ fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
+ }
+}
+
+
+
// 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 (
+ "log"
+
+ "github.com/gin-gonic/gin/binding"
+)
+
+// BindWith binds the passed struct pointer using the specified binding engine.
+// See the binding package.
+//
+// Deprecated: Use MustBindWith or ShouldBindWith.
+func (c *Context) BindWith(obj any, b binding.Binding) error {
+ log.Println(`BindWith(\"any, binding.Binding\") error is going to
+ be deprecated, please check issue #662 and either use MustBindWith() if you
+ want HTTP 400 to be automatically returned if any error occur, or use
+ ShouldBindWith() if you need to manage the error.`)
+ return c.MustBindWith(obj, b)
+}
+
+
+
// 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 (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "github.com/gin-gonic/gin/codec/json"
+)
+
+// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
+type ErrorType uint64
+
+const (
+ // ErrorTypeBind is used when Context.Bind() fails.
+ ErrorTypeBind ErrorType = 1 << 63
+ // ErrorTypeRender is used when Context.Render() fails.
+ ErrorTypeRender ErrorType = 1 << 62
+ // ErrorTypePrivate indicates a private error.
+ ErrorTypePrivate ErrorType = 1 << 0
+ // ErrorTypePublic indicates a public error.
+ ErrorTypePublic ErrorType = 1 << 1
+ // ErrorTypeAny indicates any other error.
+ ErrorTypeAny ErrorType = 1<<64 - 1
+ // ErrorTypeNu indicates any other error.
+ ErrorTypeNu = 2
+)
+
+// Error represents a error's specification.
+type Error struct {
+ Err error
+ Type ErrorType
+ Meta any
+}
+
+type errorMsgs []*Error
+
+var _ error = (*Error)(nil)
+
+// SetType sets the error's type.
+func (msg *Error) SetType(flags ErrorType) *Error {
+ msg.Type = flags
+ return msg
+}
+
+// SetMeta sets the error's meta data.
+func (msg *Error) SetMeta(data any) *Error {
+ msg.Meta = data
+ return msg
+}
+
+// JSON creates a properly formatted JSON
+func (msg *Error) JSON() any {
+ jsonData := H{}
+ if msg.Meta != nil {
+ value := reflect.ValueOf(msg.Meta)
+ switch value.Kind() {
+ case reflect.Struct:
+ return msg.Meta
+ case reflect.Map:
+ for _, key := range value.MapKeys() {
+ jsonData[key.String()] = value.MapIndex(key).Interface()
+ }
+ default:
+ jsonData["meta"] = msg.Meta
+ }
+ }
+ if _, ok := jsonData["error"]; !ok {
+ jsonData["error"] = msg.Error()
+ }
+ return jsonData
+}
+
+// MarshalJSON implements the json.Marshaller interface.
+func (msg *Error) MarshalJSON() ([]byte, error) {
+ return json.API.Marshal(msg.JSON())
+}
+
+// Error implements the error interface.
+func (msg Error) Error() string {
+ return msg.Err.Error()
+}
+
+// IsType judges one error.
+func (msg *Error) IsType(flags ErrorType) bool {
+ return (msg.Type & flags) > 0
+}
+
+// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()
+func (msg Error) Unwrap() error {
+ return msg.Err
+}
+
+// ByType returns a readonly copy filtered the byte.
+// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
+func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
+ if len(a) == 0 {
+ return nil
+ }
+ if typ == ErrorTypeAny {
+ return a
+ }
+ var result errorMsgs
+ for _, msg := range a {
+ if msg.IsType(typ) {
+ result = append(result, msg)
+ }
+ }
+ return result
+}
+
+// Last returns the last error in the slice. It returns nil if the array is empty.
+// Shortcut for errors[len(errors)-1].
+func (a errorMsgs) Last() *Error {
+ if length := len(a); length > 0 {
+ return a[length-1]
+ }
+ return nil
+}
+
+// Errors returns an array with all the error messages.
+// Example:
+//
+// c.Error(errors.New("first"))
+// c.Error(errors.New("second"))
+// c.Error(errors.New("third"))
+// c.Errors.Errors() // == []string{"first", "second", "third"}
+func (a errorMsgs) Errors() []string {
+ if len(a) == 0 {
+ return nil
+ }
+ errorStrings := make([]string, len(a))
+ for i, err := range a {
+ errorStrings[i] = err.Error()
+ }
+ return errorStrings
+}
+
+func (a errorMsgs) JSON() any {
+ switch length := len(a); length {
+ case 0:
+ return nil
+ case 1:
+ return a.Last().JSON()
+ default:
+ jsonData := make([]any, length)
+ for i, err := range a {
+ jsonData[i] = err.JSON()
+ }
+ return jsonData
+ }
+}
+
+// MarshalJSON implements the json.Marshaller interface.
+func (a errorMsgs) MarshalJSON() ([]byte, error) {
+ return json.API.Marshal(a.JSON())
+}
+
+func (a errorMsgs) String() string {
+ if len(a) == 0 {
+ return ""
+ }
+ var buffer strings.Builder
+ for i, msg := range a {
+ fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
+ if msg.Meta != nil {
+ fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
+ }
+ }
+ return buffer.String()
+}
+
+
+
// Copyright 2017 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 (
+ "net/http"
+ "os"
+)
+
+// OnlyFilesFS implements an http.FileSystem without `Readdir` functionality.
+type OnlyFilesFS struct {
+ FileSystem http.FileSystem
+}
+
+// Open passes `Open` to the upstream implementation without `Readdir` functionality.
+func (o OnlyFilesFS) Open(name string) (http.File, error) {
+ f, err := o.FileSystem.Open(name)
+ if err != nil {
+ return nil, err
+ }
+
+ return neutralizedReaddirFile{f}, nil
+}
+
+// neutralizedReaddirFile wraps http.File with a specific implementation of `Readdir`.
+type neutralizedReaddirFile struct {
+ http.File
+}
+
+// Readdir overrides the http.File default implementation and always returns nil.
+func (n neutralizedReaddirFile) Readdir(_ int) ([]os.FileInfo, error) {
+ // this disables directory listing
+ return nil, nil
+}
+
+// Dir returns an http.FileSystem that can be used by http.FileServer().
+// It is used internally in router.Static().
+// if listDirectory == true, then it works the same as http.Dir(),
+// otherwise it returns a filesystem that prevents http.FileServer() to list the directory files.
+func Dir(root string, listDirectory bool) http.FileSystem {
+ fs := http.Dir(root)
+
+ if listDirectory {
+ return fs
+ }
+
+ return &OnlyFilesFS{FileSystem: fs}
+}
+
+
+
// 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 (
+ "fmt"
+ "html/template"
+ "net"
+ "net/http"
+ "os"
+ "path"
+ "regexp"
+ "strings"
+ "sync"
+
+ "github.com/gin-gonic/gin/internal/bytesconv"
+ filesystem "github.com/gin-gonic/gin/internal/fs"
+ "github.com/gin-gonic/gin/render"
+ "github.com/quic-go/quic-go/http3"
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/h2c"
+)
+
+const defaultMultipartMemory = 32 << 20 // 32 MB
+const escapedColon = "\\:"
+const colon = ":"
+const backslash = "\\"
+
+var (
+ default404Body = []byte("404 page not found")
+ default405Body = []byte("405 method not allowed")
+)
+
+var defaultPlatform string
+
+var defaultTrustedCIDRs = []*net.IPNet{
+ { // 0.0.0.0/0 (IPv4)
+ IP: net.IP{0x0, 0x0, 0x0, 0x0},
+ Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
+ },
+ { // ::/0 (IPv6)
+ IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+ Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
+ },
+}
+
+var regSafePrefix = regexp.MustCompile("[^a-zA-Z0-9/-]+")
+var regRemoveRepeatedChar = regexp.MustCompile("/{2,}")
+
+// HandlerFunc defines the handler used by gin middleware as return value.
+type HandlerFunc func(*Context)
+
+// OptionFunc defines the function to change the default configuration
+type OptionFunc func(*Engine)
+
+// HandlersChain defines a HandlerFunc slice.
+type HandlersChain []HandlerFunc
+
+// Last returns the last handler in the chain. i.e. the last handler is the main one.
+func (c HandlersChain) Last() HandlerFunc {
+ if length := len(c); length > 0 {
+ return c[length-1]
+ }
+ return nil
+}
+
+// RouteInfo represents a request route's specification which contains method and path and its handler.
+type RouteInfo struct {
+ Method string
+ Path string
+ Handler string
+ HandlerFunc HandlerFunc
+}
+
+// RoutesInfo defines a RouteInfo slice.
+type RoutesInfo []RouteInfo
+
+// Trusted platforms
+const (
+ // PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
+ // for determining the client's IP
+ PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
+ // PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
+ // the client's IP
+ PlatformCloudflare = "CF-Connecting-IP"
+ // PlatformFlyIO when running on Fly.io. Trust Fly-Client-IP for determining the client's IP
+ PlatformFlyIO = "Fly-Client-IP"
+)
+
+// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
+// Create an instance of Engine, by using New() or Default()
+type Engine struct {
+ RouterGroup
+
+ // RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
+ // handler for the path with (without) the trailing slash exists.
+ // For example if /foo/ is requested but a route only exists for /foo, the
+ // client is redirected to /foo with http status code 301 for GET requests
+ // and 307 for all other request methods.
+ RedirectTrailingSlash bool
+
+ // RedirectFixedPath if enabled, the router tries to fix the current request path, if no
+ // handle is registered for it.
+ // First superfluous path elements like ../ or // are removed.
+ // Afterwards the router does a case-insensitive lookup of the cleaned path.
+ // If a handle can be found for this route, the router makes a redirection
+ // to the corrected path with status code 301 for GET requests and 307 for
+ // all other request methods.
+ // For example /FOO and /..//Foo could be redirected to /foo.
+ // RedirectTrailingSlash is independent of this option.
+ RedirectFixedPath bool
+
+ // HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
+ // current route, if the current request can not be routed.
+ // If this is the case, the request is answered with 'Method Not Allowed'
+ // and HTTP status code 405.
+ // If no other Method is allowed, the request is delegated to the NotFound
+ // handler.
+ HandleMethodNotAllowed bool
+
+ // ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
+ // match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
+ // fetched, it falls back to the IP obtained from
+ // `(*gin.Context).Request.RemoteAddr`.
+ ForwardedByClientIP bool
+
+ // AppEngine was deprecated.
+ // Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
+ // #726 #755 If enabled, it will trust some headers starting with
+ // 'X-AppEngine...' for better integration with that PaaS.
+ AppEngine bool
+
+ // UseRawPath if enabled, the url.RawPath will be used to find parameters.
+ UseRawPath bool
+
+ // UnescapePathValues if true, the path value will be unescaped.
+ // If UseRawPath is false (by default), the UnescapePathValues effectively is true,
+ // as url.Path gonna be used, which is already unescaped.
+ UnescapePathValues bool
+
+ // RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
+ // See the PR #1817 and issue #1644
+ RemoveExtraSlash bool
+
+ // RemoteIPHeaders list of headers used to obtain the client IP when
+ // `(*gin.Engine).ForwardedByClientIP` is `true` and
+ // `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
+ // network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
+ RemoteIPHeaders []string
+
+ // TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
+ // that platform, for example to determine the client IP
+ TrustedPlatform string
+
+ // MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
+ // method call.
+ MaxMultipartMemory int64
+
+ // UseH2C enable h2c support.
+ UseH2C bool
+
+ // ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
+ ContextWithFallback bool
+
+ delims render.Delims
+ secureJSONPrefix string
+ HTMLRender render.HTMLRender
+ FuncMap template.FuncMap
+ allNoRoute HandlersChain
+ allNoMethod HandlersChain
+ noRoute HandlersChain
+ noMethod HandlersChain
+ pool sync.Pool
+ trees methodTrees
+ maxParams uint16
+ maxSections uint16
+ trustedProxies []string
+ trustedCIDRs []*net.IPNet
+}
+
+var _ IRouter = (*Engine)(nil)
+
+// New returns a new blank Engine instance without any middleware attached.
+// By default, the configuration is:
+// - RedirectTrailingSlash: true
+// - RedirectFixedPath: false
+// - HandleMethodNotAllowed: false
+// - ForwardedByClientIP: true
+// - UseRawPath: false
+// - UnescapePathValues: true
+func New(opts ...OptionFunc) *Engine {
+ debugPrintWARNINGNew()
+ engine := &Engine{
+ RouterGroup: RouterGroup{
+ Handlers: nil,
+ basePath: "/",
+ root: true,
+ },
+ FuncMap: template.FuncMap{},
+ RedirectTrailingSlash: true,
+ RedirectFixedPath: false,
+ HandleMethodNotAllowed: false,
+ ForwardedByClientIP: true,
+ RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
+ TrustedPlatform: defaultPlatform,
+ UseRawPath: false,
+ RemoveExtraSlash: false,
+ UnescapePathValues: true,
+ MaxMultipartMemory: defaultMultipartMemory,
+ trees: make(methodTrees, 0, 9),
+ delims: render.Delims{Left: "{{", Right: "}}"},
+ secureJSONPrefix: "while(1);",
+ trustedProxies: []string{"0.0.0.0/0", "::/0"},
+ trustedCIDRs: defaultTrustedCIDRs,
+ }
+ engine.engine = engine
+ engine.pool.New = func() any {
+ return engine.allocateContext(engine.maxParams)
+ }
+ return engine.With(opts...)
+}
+
+// Default returns an Engine instance with the Logger and Recovery middleware already attached.
+func Default(opts ...OptionFunc) *Engine {
+ debugPrintWARNINGDefault()
+ engine := New()
+ engine.Use(Logger(), Recovery())
+ return engine.With(opts...)
+}
+
+func (engine *Engine) Handler() http.Handler {
+ if !engine.UseH2C {
+ return engine
+ }
+
+ h2s := &http2.Server{}
+ return h2c.NewHandler(engine, h2s)
+}
+
+func (engine *Engine) allocateContext(maxParams uint16) *Context {
+ v := make(Params, 0, maxParams)
+ skippedNodes := make([]skippedNode, 0, engine.maxSections)
+ return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
+}
+
+// Delims sets template left and right delims and returns an Engine instance.
+func (engine *Engine) Delims(left, right string) *Engine {
+ engine.delims = render.Delims{Left: left, Right: right}
+ return engine
+}
+
+// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
+func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
+ engine.secureJSONPrefix = prefix
+ return engine
+}
+
+// LoadHTMLGlob loads HTML files identified by glob pattern
+// and associates the result with HTML renderer.
+func (engine *Engine) LoadHTMLGlob(pattern string) {
+ left := engine.delims.Left
+ right := engine.delims.Right
+ templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
+
+ if IsDebugging() {
+ debugPrintLoadTemplate(templ)
+ engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
+ return
+ }
+
+ engine.SetHTMLTemplate(templ)
+}
+
+// LoadHTMLFiles loads a slice of HTML files
+// and associates the result with HTML renderer.
+func (engine *Engine) LoadHTMLFiles(files ...string) {
+ if IsDebugging() {
+ engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
+ return
+ }
+
+ templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
+ engine.SetHTMLTemplate(templ)
+}
+
+// LoadHTMLFS loads an http.FileSystem and a slice of patterns
+// and associates the result with HTML renderer.
+func (engine *Engine) LoadHTMLFS(fs http.FileSystem, patterns ...string) {
+ if IsDebugging() {
+ engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
+ return
+ }
+
+ templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(
+ filesystem.FileSystem{FileSystem: fs}, patterns...))
+ engine.SetHTMLTemplate(templ)
+}
+
+// SetHTMLTemplate associate a template with HTML renderer.
+func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
+ if len(engine.trees) > 0 {
+ debugPrintWARNINGSetHTMLTemplate()
+ }
+
+ engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
+}
+
+// SetFuncMap sets the FuncMap used for template.FuncMap.
+func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
+ engine.FuncMap = funcMap
+}
+
+// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
+func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
+ engine.noRoute = handlers
+ engine.rebuild404Handlers()
+}
+
+// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
+func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
+ engine.noMethod = handlers
+ engine.rebuild405Handlers()
+}
+
+// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
+// included in the handlers chain for every single request. Even 404, 405, static files...
+// For example, this is the right place for a logger or error management middleware.
+func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
+ engine.RouterGroup.Use(middleware...)
+ engine.rebuild404Handlers()
+ engine.rebuild405Handlers()
+ return engine
+}
+
+// With returns an Engine with the configuration set in the OptionFunc.
+func (engine *Engine) With(opts ...OptionFunc) *Engine {
+ for _, opt := range opts {
+ opt(engine)
+ }
+
+ return engine
+}
+
+func (engine *Engine) rebuild404Handlers() {
+ engine.allNoRoute = engine.combineHandlers(engine.noRoute)
+}
+
+func (engine *Engine) rebuild405Handlers() {
+ engine.allNoMethod = engine.combineHandlers(engine.noMethod)
+}
+
+func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
+ assert1(path[0] == '/', "path must begin with '/'")
+ assert1(method != "", "HTTP method can not be empty")
+ assert1(len(handlers) > 0, "there must be at least one handler")
+
+ debugPrintRoute(method, path, handlers)
+
+ root := engine.trees.get(method)
+ if root == nil {
+ root = new(node)
+ root.fullPath = "/"
+ engine.trees = append(engine.trees, methodTree{method: method, root: root})
+ }
+ root.addRoute(path, handlers)
+
+ if paramsCount := countParams(path); paramsCount > engine.maxParams {
+ engine.maxParams = paramsCount
+ }
+
+ if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
+ engine.maxSections = sectionsCount
+ }
+}
+
+// Routes returns a slice of registered routes, including some useful information, such as:
+// the http method, path, and the handler name.
+func (engine *Engine) Routes() (routes RoutesInfo) {
+ for _, tree := range engine.trees {
+ routes = iterate("", tree.method, routes, tree.root)
+ }
+ return routes
+}
+
+func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
+ path += root.path
+ if len(root.handlers) > 0 {
+ handlerFunc := root.handlers.Last()
+ routes = append(routes, RouteInfo{
+ Method: method,
+ Path: path,
+ Handler: nameOfFunction(handlerFunc),
+ HandlerFunc: handlerFunc,
+ })
+ }
+ for _, child := range root.children {
+ routes = iterate(path, method, routes, child)
+ }
+ return routes
+}
+
+func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
+ if engine.trustedProxies == nil {
+ return nil, nil
+ }
+
+ cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
+ for _, trustedProxy := range engine.trustedProxies {
+ if !strings.Contains(trustedProxy, "/") {
+ ip := parseIP(trustedProxy)
+ if ip == nil {
+ return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
+ }
+
+ switch len(ip) {
+ case net.IPv4len:
+ trustedProxy += "/32"
+ case net.IPv6len:
+ trustedProxy += "/128"
+ }
+ }
+ _, cidrNet, err := net.ParseCIDR(trustedProxy)
+ if err != nil {
+ return cidr, err
+ }
+ cidr = append(cidr, cidrNet)
+ }
+ return cidr, nil
+}
+
+// SetTrustedProxies set a list of network origins (IPv4 addresses,
+// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
+// request's headers that contain alternative client IP when
+// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
+// feature is enabled by default, and it also trusts all proxies
+// by default. If you want to disable this feature, use
+// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
+// return the remote address directly.
+func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
+ engine.trustedProxies = trustedProxies
+ return engine.parseTrustedProxies()
+}
+
+// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
+func (engine *Engine) isUnsafeTrustedProxies() bool {
+ return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
+}
+
+// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
+func (engine *Engine) parseTrustedProxies() error {
+ trustedCIDRs, err := engine.prepareTrustedCIDRs()
+ engine.trustedCIDRs = trustedCIDRs
+ return err
+}
+
+// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
+func (engine *Engine) isTrustedProxy(ip net.IP) bool {
+ if engine.trustedCIDRs == nil {
+ return false
+ }
+ for _, cidr := range engine.trustedCIDRs {
+ if cidr.Contains(ip) {
+ return true
+ }
+ }
+ return false
+}
+
+// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
+func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {
+ if header == "" {
+ return "", false
+ }
+ items := strings.Split(header, ",")
+ for i := len(items) - 1; i >= 0; i-- {
+ ipStr := strings.TrimSpace(items[i])
+ ip := net.ParseIP(ipStr)
+ if ip == nil {
+ break
+ }
+
+ // X-Forwarded-For is appended by proxy
+ // Check IPs in reverse order and stop when find untrusted proxy
+ if (i == 0) || (!engine.isTrustedProxy(ip)) {
+ return ipStr, true
+ }
+ }
+ return "", false
+}
+
+// updateRouteTree do update to the route tree recursively
+func updateRouteTree(n *node) {
+ n.path = strings.ReplaceAll(n.path, escapedColon, colon)
+ n.fullPath = strings.ReplaceAll(n.fullPath, escapedColon, colon)
+ n.indices = strings.ReplaceAll(n.indices, backslash, colon)
+ if n.children == nil {
+ return
+ }
+ for _, child := range n.children {
+ updateRouteTree(child)
+ }
+}
+
+// updateRouteTrees do update to the route trees
+func (engine *Engine) updateRouteTrees() {
+ for _, tree := range engine.trees {
+ updateRouteTree(tree.root)
+ }
+}
+
+// parseIP parse a string representation of an IP and returns a net.IP with the
+// minimum byte representation or nil if input is invalid.
+func parseIP(ip string) net.IP {
+ parsedIP := net.ParseIP(ip)
+
+ if ipv4 := parsedIP.To4(); ipv4 != nil {
+ // return ip in a 4-byte representation
+ return ipv4
+ }
+
+ // return ip in a 16-byte representation or nil
+ return parsedIP
+}
+
+// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
+// It is a shortcut for http.ListenAndServe(addr, router)
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func (engine *Engine) Run(addr ...string) (err error) {
+ defer func() { debugPrintError(err) }()
+
+ if engine.isUnsafeTrustedProxies() {
+ debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+ "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
+ }
+ engine.updateRouteTrees()
+ address := resolveAddress(addr)
+ debugPrint("Listening and serving HTTP on %s\n", address)
+ err = http.ListenAndServe(address, engine.Handler())
+ return
+}
+
+// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
+// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
+ debugPrint("Listening and serving HTTPS on %s\n", addr)
+ defer func() { debugPrintError(err) }()
+
+ if engine.isUnsafeTrustedProxies() {
+ debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+ "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
+ }
+
+ err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
+ return
+}
+
+// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
+// through the specified unix socket (i.e. a file).
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func (engine *Engine) RunUnix(file string) (err error) {
+ debugPrint("Listening and serving HTTP on unix:/%s", file)
+ defer func() { debugPrintError(err) }()
+
+ if engine.isUnsafeTrustedProxies() {
+ debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+ "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
+ }
+
+ listener, err := net.Listen("unix", file)
+ if err != nil {
+ return
+ }
+ defer listener.Close()
+ defer os.Remove(file)
+
+ err = http.Serve(listener, engine.Handler())
+ return
+}
+
+// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
+// through the specified file descriptor.
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func (engine *Engine) RunFd(fd int) (err error) {
+ debugPrint("Listening and serving HTTP on fd@%d", fd)
+ defer func() { debugPrintError(err) }()
+
+ if engine.isUnsafeTrustedProxies() {
+ debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+ "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
+ }
+
+ f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
+ listener, err := net.FileListener(f)
+ if err != nil {
+ return
+ }
+ defer listener.Close()
+ err = engine.RunListener(listener)
+ return
+}
+
+// RunQUIC attaches the router to a http.Server and starts listening and serving QUIC requests.
+// It is a shortcut for http3.ListenAndServeQUIC(addr, certFile, keyFile, router)
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func (engine *Engine) RunQUIC(addr, certFile, keyFile string) (err error) {
+ debugPrint("Listening and serving QUIC on %s\n", addr)
+ defer func() { debugPrintError(err) }()
+
+ if engine.isUnsafeTrustedProxies() {
+ debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+ "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
+ }
+
+ err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler())
+ return
+}
+
+// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
+// through the specified net.Listener
+func (engine *Engine) RunListener(listener net.Listener) (err error) {
+ debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
+ defer func() { debugPrintError(err) }()
+
+ if engine.isUnsafeTrustedProxies() {
+ debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
+ "Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
+ }
+
+ err = http.Serve(listener, engine.Handler())
+ return
+}
+
+// ServeHTTP conforms to the http.Handler interface.
+func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ c := engine.pool.Get().(*Context)
+ c.writermem.reset(w)
+ c.Request = req
+ c.reset()
+
+ engine.handleHTTPRequest(c)
+
+ engine.pool.Put(c)
+}
+
+// HandleContext re-enters a context that has been rewritten.
+// This can be done by setting c.Request.URL.Path to your new target.
+// Disclaimer: You can loop yourself to deal with this, use wisely.
+func (engine *Engine) HandleContext(c *Context) {
+ oldIndexValue := c.index
+ oldHandlers := c.handlers
+ c.reset()
+ engine.handleHTTPRequest(c)
+
+ c.index = oldIndexValue
+ c.handlers = oldHandlers
+}
+
+func (engine *Engine) handleHTTPRequest(c *Context) {
+ httpMethod := c.Request.Method
+ rPath := c.Request.URL.Path
+ unescape := false
+ if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
+ rPath = c.Request.URL.RawPath
+ unescape = engine.UnescapePathValues
+ }
+
+ if engine.RemoveExtraSlash {
+ rPath = cleanPath(rPath)
+ }
+
+ // Find root of the tree for the given HTTP method
+ t := engine.trees
+ for i, tl := 0, len(t); i < tl; i++ {
+ if t[i].method != httpMethod {
+ continue
+ }
+ root := t[i].root
+ // Find route in tree
+ value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
+ if value.params != nil {
+ c.Params = *value.params
+ }
+ if value.handlers != nil {
+ c.handlers = value.handlers
+ c.fullPath = value.fullPath
+ c.Next()
+ c.writermem.WriteHeaderNow()
+ return
+ }
+ if httpMethod != http.MethodConnect && rPath != "/" {
+ if value.tsr && engine.RedirectTrailingSlash {
+ redirectTrailingSlash(c)
+ return
+ }
+ if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
+ return
+ }
+ }
+ break
+ }
+
+ if engine.HandleMethodNotAllowed && len(t) > 0 {
+ // According to RFC 7231 section 6.5.5, MUST generate an Allow header field in response
+ // containing a list of the target resource's currently supported methods.
+ allowed := make([]string, 0, len(t)-1)
+ for _, tree := range engine.trees {
+ if tree.method == httpMethod {
+ continue
+ }
+ if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
+ allowed = append(allowed, tree.method)
+ }
+ }
+ if len(allowed) > 0 {
+ c.handlers = engine.allNoMethod
+ c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
+ serveError(c, http.StatusMethodNotAllowed, default405Body)
+ return
+ }
+ }
+
+ c.handlers = engine.allNoRoute
+ serveError(c, http.StatusNotFound, default404Body)
+}
+
+var mimePlain = []string{MIMEPlain}
+
+func serveError(c *Context, code int, defaultMessage []byte) {
+ c.writermem.status = code
+ c.Next()
+ if c.writermem.Written() {
+ return
+ }
+ if c.writermem.Status() == code {
+ c.writermem.Header()["Content-Type"] = mimePlain
+ _, err := c.Writer.Write(defaultMessage)
+ if err != nil {
+ debugPrint("cannot write message to writer during serve error: %v", err)
+ }
+ return
+ }
+ c.writermem.WriteHeaderNow()
+}
+
+func redirectTrailingSlash(c *Context) {
+ req := c.Request
+ p := req.URL.Path
+ if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
+ prefix = regSafePrefix.ReplaceAllString(prefix, "")
+ prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
+
+ p = prefix + "/" + req.URL.Path
+ }
+ req.URL.Path = p + "/"
+ if length := len(p); length > 1 && p[length-1] == '/' {
+ req.URL.Path = p[:length-1]
+ }
+ redirectRequest(c)
+}
+
+func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
+ req := c.Request
+ rPath := req.URL.Path
+
+ if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
+ req.URL.Path = bytesconv.BytesToString(fixedPath)
+ redirectRequest(c)
+ return true
+ }
+ return false
+}
+
+func redirectRequest(c *Context) {
+ req := c.Request
+ rPath := req.URL.Path
+ rURL := req.URL.String()
+
+ code := http.StatusMovedPermanently // Permanent redirect, request with GET method
+ if req.Method != http.MethodGet {
+ code = http.StatusTemporaryRedirect
+ }
+ debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
+ http.Redirect(c.Writer, req, rURL, code)
+ c.writermem.WriteHeaderNow()
+}
+
+
+
// 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 ginS
+
+import (
+ "html/template"
+ "net/http"
+ "sync"
+
+ "github.com/gin-gonic/gin"
+)
+
+var (
+ once sync.Once
+ internalEngine *gin.Engine
+)
+
+func engine() *gin.Engine {
+ once.Do(func() {
+ internalEngine = gin.Default()
+ })
+ return internalEngine
+}
+
+// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
+func LoadHTMLGlob(pattern string) {
+ engine().LoadHTMLGlob(pattern)
+}
+
+// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles.
+func LoadHTMLFiles(files ...string) {
+ engine().LoadHTMLFiles(files...)
+}
+
+// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS.
+func LoadHTMLFS(fs http.FileSystem, patterns ...string) {
+ engine().LoadHTMLFS(fs, patterns...)
+}
+
+// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
+func SetHTMLTemplate(templ *template.Template) {
+ engine().SetHTMLTemplate(templ)
+}
+
+// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
+func NoRoute(handlers ...gin.HandlerFunc) {
+ engine().NoRoute(handlers...)
+}
+
+// NoMethod is a wrapper for Engine.NoMethod.
+func NoMethod(handlers ...gin.HandlerFunc) {
+ engine().NoMethod(handlers...)
+}
+
+// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
+// For example, all the routes that use a common middleware for authorization could be grouped.
+func Group(relativePath string, handlers ...gin.HandlerFunc) *gin.RouterGroup {
+ return engine().Group(relativePath, handlers...)
+}
+
+// Handle is a wrapper for Engine.Handle.
+func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().Handle(httpMethod, relativePath, handlers...)
+}
+
+// POST is a shortcut for router.Handle("POST", path, handle)
+func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().POST(relativePath, handlers...)
+}
+
+// GET is a shortcut for router.Handle("GET", path, handle)
+func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().GET(relativePath, handlers...)
+}
+
+// DELETE is a shortcut for router.Handle("DELETE", path, handle)
+func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().DELETE(relativePath, handlers...)
+}
+
+// PATCH is a shortcut for router.Handle("PATCH", path, handle)
+func PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().PATCH(relativePath, handlers...)
+}
+
+// PUT is a shortcut for router.Handle("PUT", path, handle)
+func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().PUT(relativePath, handlers...)
+}
+
+// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
+func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().OPTIONS(relativePath, handlers...)
+}
+
+// HEAD is a shortcut for router.Handle("HEAD", path, handle)
+func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().HEAD(relativePath, handlers...)
+}
+
+// Any is a wrapper for Engine.Any.
+func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
+ return engine().Any(relativePath, handlers...)
+}
+
+// StaticFile is a wrapper for Engine.StaticFile.
+func StaticFile(relativePath, filepath string) gin.IRoutes {
+ return engine().StaticFile(relativePath, filepath)
+}
+
+// Static serves files from the given file system root.
+// Internally a http.FileServer is used, therefore http.NotFound is used instead
+// of the Router's NotFound handler.
+// To use the operating system's file system implementation,
+// use :
+//
+// router.Static("/static", "/var/www")
+func Static(relativePath, root string) gin.IRoutes {
+ return engine().Static(relativePath, root)
+}
+
+// StaticFS is a wrapper for Engine.StaticFS.
+func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes {
+ return engine().StaticFS(relativePath, fs)
+}
+
+// Use attaches a global middleware to the router. i.e. the middlewares attached through Use() will be
+// included in the handlers chain for every single request. Even 404, 405, static files...
+// For example, this is the right place for a logger or error management middleware.
+func Use(middlewares ...gin.HandlerFunc) gin.IRoutes {
+ return engine().Use(middlewares...)
+}
+
+// Routes returns a slice of registered routes.
+func Routes() gin.RoutesInfo {
+ return engine().Routes()
+}
+
+// Run attaches to a http.Server and starts listening and serving HTTP requests.
+// It is a shortcut for http.ListenAndServe(addr, router)
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func Run(addr ...string) (err error) {
+ return engine().Run(addr...)
+}
+
+// RunTLS attaches to a http.Server and starts listening and serving HTTPS requests.
+// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func RunTLS(addr, certFile, keyFile string) (err error) {
+ return engine().RunTLS(addr, certFile, keyFile)
+}
+
+// RunUnix attaches to a http.Server and starts listening and serving HTTP requests
+// through the specified unix socket (i.e. a file)
+// Note: this method will block the calling goroutine indefinitely unless an error happens.
+func RunUnix(file string) (err error) {
+ return engine().RunUnix(file)
+}
+
+// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
+// through the specified file descriptor.
+// Note: the method will block the calling goroutine indefinitely unless an error happens.
+func RunFd(fd int) (err error) {
+ return engine().RunFd(fd)
+}
+
+
+
// Copyright 2023 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package bytesconv
+
+import (
+ "unsafe"
+)
+
+// StringToBytes converts string to byte slice without a memory allocation.
+// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
+func StringToBytes(s string) []byte {
+ return unsafe.Slice(unsafe.StringData(s), len(s))
+}
+
+// BytesToString converts byte slice to string without a memory allocation.
+// For more details, see https://github.com/golang/go/issues/53003#issuecomment-1140276077.
+func BytesToString(b []byte) string {
+ return unsafe.String(unsafe.SliceData(b), len(b))
+}
+
+
+
package fs
+
+import (
+ "io/fs"
+ "net/http"
+)
+
+// FileSystem implements an [fs.FS].
+type FileSystem struct {
+ http.FileSystem
+}
+
+// Open passes `Open` to the upstream implementation and return an [fs.File].
+func (o FileSystem) Open(name string) (fs.File, error) {
+ f, err := o.FileSystem.Open(name)
+ if err != nil {
+ return nil, err
+ }
+
+ return fs.File(f), nil
+}
+
+
+
// 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 (
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/mattn/go-isatty"
+)
+
+type consoleColorModeValue int
+
+const (
+ autoColor consoleColorModeValue = iota
+ disableColor
+ forceColor
+)
+
+const (
+ green = "\033[97;42m"
+ white = "\033[90;47m"
+ yellow = "\033[90;43m"
+ red = "\033[97;41m"
+ blue = "\033[97;44m"
+ magenta = "\033[97;45m"
+ cyan = "\033[97;46m"
+ reset = "\033[0m"
+)
+
+var consoleColorMode = autoColor
+
+// LoggerConfig defines the config for Logger middleware.
+type LoggerConfig struct {
+ // Optional. Default value is gin.defaultLogFormatter
+ Formatter LogFormatter
+
+ // Output is a writer where logs are written.
+ // Optional. Default value is gin.DefaultWriter.
+ Output io.Writer
+
+ // SkipPaths is an url path array which logs are not written.
+ // Optional.
+ SkipPaths []string
+
+ // Skip is a Skipper that indicates which logs should not be written.
+ // Optional.
+ Skip Skipper
+}
+
+// Skipper is a function to skip logs based on provided Context
+type Skipper func(c *Context) bool
+
+// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
+type LogFormatter func(params LogFormatterParams) string
+
+// LogFormatterParams is the structure any formatter will be handed when time to log comes
+type LogFormatterParams struct {
+ Request *http.Request
+
+ // TimeStamp shows the time after the server returns a response.
+ TimeStamp time.Time
+ // StatusCode is HTTP response code.
+ StatusCode int
+ // Latency is how much time the server cost to process a certain request.
+ Latency time.Duration
+ // ClientIP equals Context's ClientIP method.
+ ClientIP string
+ // Method is the HTTP method given to the request.
+ Method string
+ // Path is a path the client requests.
+ Path string
+ // ErrorMessage is set if error has occurred in processing the request.
+ ErrorMessage string
+ // isTerm shows whether gin's output descriptor refers to a terminal.
+ isTerm bool
+ // BodySize is the size of the Response Body
+ BodySize int
+ // Keys are the keys set on the request's context.
+ Keys map[any]any
+}
+
+// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
+func (p *LogFormatterParams) StatusCodeColor() string {
+ code := p.StatusCode
+
+ switch {
+ case code >= http.StatusContinue && code < http.StatusOK:
+ return white
+ case code >= http.StatusOK && code < http.StatusMultipleChoices:
+ return green
+ case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
+ return white
+ case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
+ return yellow
+ default:
+ return red
+ }
+}
+
+// MethodColor is the ANSI color for appropriately logging http method to a terminal.
+func (p *LogFormatterParams) MethodColor() string {
+ method := p.Method
+
+ switch method {
+ case http.MethodGet:
+ return blue
+ case http.MethodPost:
+ return cyan
+ case http.MethodPut:
+ return yellow
+ case http.MethodDelete:
+ return red
+ case http.MethodPatch:
+ return green
+ case http.MethodHead:
+ return magenta
+ case http.MethodOptions:
+ return white
+ default:
+ return reset
+ }
+}
+
+// ResetColor resets all escape attributes.
+func (p *LogFormatterParams) ResetColor() string {
+ return reset
+}
+
+// IsOutputColor indicates whether can colors be outputted to the log.
+func (p *LogFormatterParams) IsOutputColor() bool {
+ return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
+}
+
+// defaultLogFormatter is the default log format function Logger middleware uses.
+var defaultLogFormatter = func(param LogFormatterParams) string {
+ var statusColor, methodColor, resetColor string
+ if param.IsOutputColor() {
+ statusColor = param.StatusCodeColor()
+ methodColor = param.MethodColor()
+ resetColor = param.ResetColor()
+ }
+
+ if param.Latency > time.Minute {
+ param.Latency = param.Latency.Truncate(time.Second)
+ }
+ return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
+ param.TimeStamp.Format("2006/01/02 - 15:04:05"),
+ statusColor, param.StatusCode, resetColor,
+ param.Latency,
+ param.ClientIP,
+ methodColor, param.Method, resetColor,
+ param.Path,
+ param.ErrorMessage,
+ )
+}
+
+// DisableConsoleColor disables color output in the console.
+func DisableConsoleColor() {
+ consoleColorMode = disableColor
+}
+
+// ForceConsoleColor force color output in the console.
+func ForceConsoleColor() {
+ consoleColorMode = forceColor
+}
+
+// ErrorLogger returns a HandlerFunc for any error type.
+func ErrorLogger() HandlerFunc {
+ return ErrorLoggerT(ErrorTypeAny)
+}
+
+// ErrorLoggerT returns a HandlerFunc for a given error type.
+func ErrorLoggerT(typ ErrorType) HandlerFunc {
+ return func(c *Context) {
+ c.Next()
+ errors := c.Errors.ByType(typ)
+ if len(errors) > 0 {
+ c.JSON(-1, errors)
+ }
+ }
+}
+
+// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
+// By default, gin.DefaultWriter = os.Stdout.
+func Logger() HandlerFunc {
+ return LoggerWithConfig(LoggerConfig{})
+}
+
+// LoggerWithFormatter instance a Logger middleware with the specified log format function.
+func LoggerWithFormatter(f LogFormatter) HandlerFunc {
+ return LoggerWithConfig(LoggerConfig{
+ Formatter: f,
+ })
+}
+
+// LoggerWithWriter instance a Logger middleware with the specified writer buffer.
+// Example: os.Stdout, a file opened in write mode, a socket...
+func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
+ return LoggerWithConfig(LoggerConfig{
+ Output: out,
+ SkipPaths: notlogged,
+ })
+}
+
+// LoggerWithConfig instance a Logger middleware with config.
+func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
+ formatter := conf.Formatter
+ if formatter == nil {
+ formatter = defaultLogFormatter
+ }
+
+ out := conf.Output
+ if out == nil {
+ out = DefaultWriter
+ }
+
+ notlogged := conf.SkipPaths
+
+ isTerm := true
+
+ if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
+ (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
+ isTerm = false
+ }
+
+ var skip map[string]struct{}
+
+ if length := len(notlogged); length > 0 {
+ skip = make(map[string]struct{}, length)
+
+ for _, path := range notlogged {
+ skip[path] = struct{}{}
+ }
+ }
+
+ return func(c *Context) {
+ // Start timer
+ start := time.Now()
+ path := c.Request.URL.Path
+ raw := c.Request.URL.RawQuery
+
+ // Process request
+ c.Next()
+
+ // Log only when it is not being skipped
+ if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) {
+ return
+ }
+
+ param := LogFormatterParams{
+ Request: c.Request,
+ isTerm: isTerm,
+ Keys: c.Keys,
+ }
+
+ // Stop timer
+ param.TimeStamp = time.Now()
+ param.Latency = param.TimeStamp.Sub(start)
+
+ param.ClientIP = c.ClientIP()
+ param.Method = c.Request.Method
+ param.StatusCode = c.Writer.Status()
+ param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
+
+ param.BodySize = c.Writer.Size()
+
+ if raw != "" {
+ path = path + "?" + raw
+ }
+
+ param.Path = path
+
+ fmt.Fprint(out, formatter(param))
+ }
+}
+
+
+
// 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 (
+ "flag"
+ "io"
+ "os"
+ "sync/atomic"
+
+ "github.com/gin-gonic/gin/binding"
+)
+
+// EnvGinMode indicates environment name for gin mode.
+const EnvGinMode = "GIN_MODE"
+
+const (
+ // DebugMode indicates gin mode is debug.
+ DebugMode = "debug"
+ // ReleaseMode indicates gin mode is release.
+ ReleaseMode = "release"
+ // TestMode indicates gin mode is test.
+ TestMode = "test"
+)
+
+const (
+ debugCode = iota
+ releaseCode
+ testCode
+)
+
+// DefaultWriter is the default io.Writer used by Gin for debug output and
+// middleware output like Logger() or Recovery().
+// Note that both Logger and Recovery provides custom ways to configure their
+// output io.Writer.
+// To support coloring in Windows use:
+//
+// import "github.com/mattn/go-colorable"
+// gin.DefaultWriter = colorable.NewColorableStdout()
+var DefaultWriter io.Writer = os.Stdout
+
+// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
+var DefaultErrorWriter io.Writer = os.Stderr
+
+var (
+ ginMode int32 = debugCode
+ modeName atomic.Value
+)
+
+func init() {
+ mode := os.Getenv(EnvGinMode)
+ SetMode(mode)
+}
+
+// SetMode sets gin mode according to input string.
+func SetMode(value string) {
+ if value == "" {
+ if flag.Lookup("test.v") != nil {
+ value = TestMode
+ } else {
+ value = DebugMode
+ }
+ }
+
+ switch value {
+ case DebugMode, "":
+ atomic.StoreInt32(&ginMode, debugCode)
+ case ReleaseMode:
+ atomic.StoreInt32(&ginMode, releaseCode)
+ case TestMode:
+ atomic.StoreInt32(&ginMode, testCode)
+ default:
+ panic("gin mode unknown: " + value + " (available mode: debug release test)")
+ }
+ modeName.Store(value)
+}
+
+// DisableBindValidation closes the default validator.
+func DisableBindValidation() {
+ binding.Validator = nil
+}
+
+// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to
+// call the UseNumber method on the JSON Decoder instance.
+func EnableJsonDecoderUseNumber() {
+ binding.EnableDecoderUseNumber = true
+}
+
+// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
+// call the DisallowUnknownFields method on the JSON Decoder instance.
+func EnableJsonDecoderDisallowUnknownFields() {
+ binding.EnableDecoderDisallowUnknownFields = true
+}
+
+// Mode returns current gin mode.
+func Mode() string {
+ return modeName.Load().(string)
+}
+
+
+
// Copyright 2013 Julien Schmidt. All rights reserved.
+// Based on the path package, Copyright 2009 The Go Authors.
+// Use of this source code is governed by a BSD-style license that can be found
+// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE.
+
+package gin
+
+// cleanPath is the URL version of path.Clean, it returns a canonical URL path
+// for p, eliminating . and .. elements.
+//
+// The following rules are applied iteratively until no further processing can
+// be done:
+// 1. Replace multiple slashes with a single slash.
+// 2. Eliminate each . path name element (the current directory).
+// 3. Eliminate each inner .. path name element (the parent directory)
+// along with the non-.. element that precedes it.
+// 4. Eliminate .. elements that begin a rooted path:
+// that is, replace "/.." by "/" at the beginning of a path.
+//
+// If the result of this process is an empty string, "/" is returned.
+func cleanPath(p string) string {
+ const stackBufSize = 128
+ // Turn empty string into "/"
+ if p == "" {
+ return "/"
+ }
+
+ // Reasonably sized buffer on stack to avoid allocations in the common case.
+ // If a larger buffer is required, it gets allocated dynamically.
+ buf := make([]byte, 0, stackBufSize)
+
+ n := len(p)
+
+ // Invariants:
+ // reading from path; r is index of next byte to process.
+ // writing to buf; w is index of next byte to write.
+
+ // path must start with '/'
+ r := 1
+ w := 1
+
+ if p[0] != '/' {
+ r = 0
+
+ if n+1 > stackBufSize {
+ buf = make([]byte, n+1)
+ } else {
+ buf = buf[:n+1]
+ }
+ buf[0] = '/'
+ }
+
+ trailing := n > 1 && p[n-1] == '/'
+
+ // A bit more clunky without a 'lazybuf' like the path package, but the loop
+ // gets completely inlined (bufApp calls).
+ // loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
+ // calls (except make, if needed).
+
+ for r < n {
+ switch {
+ case p[r] == '/':
+ // empty path element, trailing slash is added after the end
+ r++
+
+ case p[r] == '.' && r+1 == n:
+ trailing = true
+ r++
+
+ case p[r] == '.' && p[r+1] == '/':
+ // . element
+ r += 2
+
+ case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
+ // .. element: remove to last /
+ r += 3
+
+ if w > 1 {
+ // can backtrack
+ w--
+
+ if len(buf) == 0 {
+ for w > 1 && p[w] != '/' {
+ w--
+ }
+ } else {
+ for w > 1 && buf[w] != '/' {
+ w--
+ }
+ }
+ }
+
+ default:
+ // Real path element.
+ // Add slash if needed
+ if w > 1 {
+ bufApp(&buf, p, w, '/')
+ w++
+ }
+
+ // Copy element
+ for r < n && p[r] != '/' {
+ bufApp(&buf, p, w, p[r])
+ w++
+ r++
+ }
+ }
+ }
+
+ // Re-append trailing slash
+ if trailing && w > 1 {
+ bufApp(&buf, p, w, '/')
+ w++
+ }
+
+ // If the original string was not modified (or only shortened at the end),
+ // return the respective substring of the original string.
+ // Otherwise return a new string from the buffer.
+ if len(buf) == 0 {
+ return p[:w]
+ }
+ return string(buf[:w])
+}
+
+// Internal helper to lazily create a buffer if necessary.
+// Calls to this function get inlined.
+func bufApp(buf *[]byte, s string, w int, c byte) {
+ b := *buf
+ if len(b) == 0 {
+ // No modification of the original string so far.
+ // If the next character is the same as in the original string, we do
+ // not yet have to allocate a buffer.
+ if s[w] == c {
+ return
+ }
+
+ // Otherwise use either the stack buffer, if it is large enough, or
+ // allocate a new buffer on the heap, and copy all previous characters.
+ length := len(s)
+ if length > cap(b) {
+ *buf = make([]byte, length)
+ } else {
+ *buf = (*buf)[:length]
+ }
+ b = *buf
+
+ copy(b, s[:w])
+ }
+ b[w] = c
+}
+
+
+
// 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 (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httputil"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+)
+
+const dunno = "???"
+
+var dunnoBytes = []byte(dunno)
+
+// RecoveryFunc defines the function passable to CustomRecovery.
+type RecoveryFunc func(c *Context, err any)
+
+// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
+func Recovery() HandlerFunc {
+ return RecoveryWithWriter(DefaultErrorWriter)
+}
+
+// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
+func CustomRecovery(handle RecoveryFunc) HandlerFunc {
+ return RecoveryWithWriter(DefaultErrorWriter, handle)
+}
+
+// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
+func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
+ if len(recovery) > 0 {
+ return CustomRecoveryWithWriter(out, recovery[0])
+ }
+ return CustomRecoveryWithWriter(out, defaultHandleRecovery)
+}
+
+// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
+func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
+ var logger *log.Logger
+ if out != nil {
+ logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
+ }
+ return func(c *Context) {
+ defer func() {
+ if err := recover(); err != nil {
+ // Check for a broken connection, as it is not really a
+ // condition that warrants a panic stack trace.
+ var brokenPipe bool
+ if ne, ok := err.(*net.OpError); ok {
+ var se *os.SyscallError
+ if errors.As(ne, &se) {
+ seStr := strings.ToLower(se.Error())
+ if strings.Contains(seStr, "broken pipe") ||
+ strings.Contains(seStr, "connection reset by peer") {
+ brokenPipe = true
+ }
+ }
+ }
+ if logger != nil {
+ stack := stack(3)
+ httpRequest, _ := httputil.DumpRequest(c.Request, false)
+ headers := strings.Split(string(httpRequest), "\r\n")
+ maskAuthorization(headers)
+ headersToStr := strings.Join(headers, "\r\n")
+ if brokenPipe {
+ logger.Printf("%s\n%s%s", err, headersToStr, reset)
+ } else if IsDebugging() {
+ logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
+ timeFormat(time.Now()), headersToStr, err, stack, reset)
+ } else {
+ logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
+ timeFormat(time.Now()), err, stack, reset)
+ }
+ }
+ if brokenPipe {
+ // If the connection is dead, we can't write a status to it.
+ c.Error(err.(error)) //nolint: errcheck
+ c.Abort()
+ } else {
+ handle(c, err)
+ }
+ }
+ }()
+ c.Next()
+ }
+}
+
+func defaultHandleRecovery(c *Context, _ any) {
+ c.AbortWithStatus(http.StatusInternalServerError)
+}
+
+// stack returns a nicely formatted stack frame, skipping skip frames.
+func stack(skip int) []byte {
+ buf := new(bytes.Buffer) // the returned data
+ // As we loop, we open files and read them. These variables record the currently
+ // loaded file.
+ var lines [][]byte
+ var lastFile string
+ for i := skip; ; i++ { // Skip the expected number of frames
+ pc, file, line, ok := runtime.Caller(i)
+ if !ok {
+ break
+ }
+ // Print this much at least. If we can't find the source, it won't show.
+ fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
+ if file != lastFile {
+ data, err := os.ReadFile(file)
+ if err != nil {
+ continue
+ }
+ lines = bytes.Split(data, []byte{'\n'})
+ lastFile = file
+ }
+ fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
+ }
+ return buf.Bytes()
+}
+
+// maskAuthorization replaces any "Authorization: <token>" header with "Authorization: *", hiding sensitive credentials.
+func maskAuthorization(headers []string) {
+ for idx, header := range headers {
+ key, _, _ := strings.Cut(header, ":")
+ if strings.EqualFold(key, "Authorization") {
+ headers[idx] = key + ": *"
+ }
+ }
+}
+
+// source returns a space-trimmed slice of the n'th line.
+func source(lines [][]byte, n int) []byte {
+ n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
+ if n < 0 || n >= len(lines) {
+ return dunnoBytes
+ }
+ return bytes.TrimSpace(lines[n])
+}
+
+// function returns, if possible, the name of the function containing the PC.
+func function(pc uintptr) string {
+ fn := runtime.FuncForPC(pc)
+ if fn == nil {
+ return dunno
+ }
+ name := fn.Name()
+ // The name includes the path name to the package, which is unnecessary
+ // since the file name is already included. Plus, it has center dots.
+ // That is, we see
+ // runtime/debug.*T·ptrmethod
+ // and want
+ // *T.ptrmethod
+ // Also the package path might contain dot (e.g. code.google.com/...),
+ // so first eliminate the path prefix
+ if lastSlash := strings.LastIndexByte(name, '/'); lastSlash >= 0 {
+ name = name[lastSlash+1:]
+ }
+ if period := strings.IndexByte(name, '.'); period >= 0 {
+ name = name[period+1:]
+ }
+ name = strings.ReplaceAll(name, "·", ".")
+ return name
+}
+
+// timeFormat returns a customized time string for logger.
+func timeFormat(t time.Time) string {
+ return t.Format("2006/01/02 - 15:04:05")
+}
+
+
+
// 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 render
+
+import "net/http"
+
+// Data contains ContentType and bytes data.
+type Data struct {
+ ContentType string
+ Data []byte
+}
+
+// Render (Data) writes data with custom ContentType.
+func (r Data) Render(w http.ResponseWriter) (err error) {
+ r.WriteContentType(w)
+ _, err = w.Write(r.Data)
+ return
+}
+
+// WriteContentType (Data) writes custom ContentType.
+func (r Data) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, []string{r.ContentType})
+}
+
+
+
// 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 render
+
+import (
+ "html/template"
+ "net/http"
+
+ "github.com/gin-gonic/gin/internal/fs"
+)
+
+// Delims represents a set of Left and Right delimiters for HTML template rendering.
+type Delims struct {
+ // Left delimiter, defaults to {{.
+ Left string
+ // Right delimiter, defaults to }}.
+ Right string
+}
+
+// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
+type HTMLRender interface {
+ // Instance returns an HTML instance.
+ Instance(string, any) Render
+}
+
+// HTMLProduction contains template reference and its delims.
+type HTMLProduction struct {
+ Template *template.Template
+ Delims Delims
+}
+
+// HTMLDebug contains template delims and pattern and function with file list.
+type HTMLDebug struct {
+ Files []string
+ Glob string
+ FileSystem http.FileSystem
+ Patterns []string
+ Delims Delims
+ FuncMap template.FuncMap
+}
+
+// HTML contains template reference and its name with given interface object.
+type HTML struct {
+ Template *template.Template
+ Name string
+ Data any
+}
+
+var htmlContentType = []string{"text/html; charset=utf-8"}
+
+// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
+func (r HTMLProduction) Instance(name string, data any) Render {
+ return HTML{
+ Template: r.Template,
+ Name: name,
+ Data: data,
+ }
+}
+
+// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
+func (r HTMLDebug) Instance(name string, data any) Render {
+ return HTML{
+ Template: r.loadTemplate(),
+ Name: name,
+ Data: data,
+ }
+}
+
+func (r HTMLDebug) loadTemplate() *template.Template {
+ if r.FuncMap == nil {
+ r.FuncMap = template.FuncMap{}
+ }
+ if len(r.Files) > 0 {
+ return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
+ }
+ if r.Glob != "" {
+ return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
+ }
+ if r.FileSystem != nil && len(r.Patterns) > 0 {
+ return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(
+ fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...))
+ }
+ panic("the HTML debug render was created without files or glob pattern or file system with patterns")
+}
+
+// Render (HTML) executes template and writes its result with custom ContentType for response.
+func (r HTML) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+
+ if r.Name == "" {
+ return r.Template.Execute(w, r.Data)
+ }
+ return r.Template.ExecuteTemplate(w, r.Name, r.Data)
+}
+
+// WriteContentType (HTML) writes HTML ContentType.
+func (r HTML) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, htmlContentType)
+}
+
+
+
// 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 render
+
+import (
+ "bytes"
+ "fmt"
+ "html/template"
+ "net/http"
+ "unicode"
+
+ "github.com/gin-gonic/gin/codec/json"
+ "github.com/gin-gonic/gin/internal/bytesconv"
+)
+
+// JSON contains the given interface object.
+type JSON struct {
+ Data any
+}
+
+// IndentedJSON contains the given interface object.
+type IndentedJSON struct {
+ Data any
+}
+
+// SecureJSON contains the given interface object and its prefix.
+type SecureJSON struct {
+ Prefix string
+ Data any
+}
+
+// JsonpJSON contains the given interface object its callback.
+type JsonpJSON struct {
+ Callback string
+ Data any
+}
+
+// AsciiJSON contains the given interface object.
+type AsciiJSON struct {
+ Data any
+}
+
+// PureJSON contains the given interface object.
+type PureJSON struct {
+ Data any
+}
+
+var (
+ jsonContentType = []string{"application/json; charset=utf-8"}
+ jsonpContentType = []string{"application/javascript; charset=utf-8"}
+ jsonASCIIContentType = []string{"application/json"}
+)
+
+// Render (JSON) writes data with custom ContentType.
+func (r JSON) Render(w http.ResponseWriter) error {
+ return WriteJSON(w, r.Data)
+}
+
+// WriteContentType (JSON) writes JSON ContentType.
+func (r JSON) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, jsonContentType)
+}
+
+// WriteJSON marshals the given interface object and writes it with custom ContentType.
+func WriteJSON(w http.ResponseWriter, obj any) error {
+ writeContentType(w, jsonContentType)
+ jsonBytes, err := json.API.Marshal(obj)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(jsonBytes)
+ return err
+}
+
+// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
+func (r IndentedJSON) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+ jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ")
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(jsonBytes)
+ return err
+}
+
+// WriteContentType (IndentedJSON) writes JSON ContentType.
+func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, jsonContentType)
+}
+
+// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
+func (r SecureJSON) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+ jsonBytes, err := json.API.Marshal(r.Data)
+ if err != nil {
+ return err
+ }
+ // if the jsonBytes is array values
+ if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
+ bytesconv.StringToBytes("]")) {
+ if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
+ return err
+ }
+ }
+ _, err = w.Write(jsonBytes)
+ return err
+}
+
+// WriteContentType (SecureJSON) writes JSON ContentType.
+func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, jsonContentType)
+}
+
+// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
+func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
+ r.WriteContentType(w)
+ ret, err := json.API.Marshal(r.Data)
+ if err != nil {
+ return err
+ }
+
+ if r.Callback == "" {
+ _, err = w.Write(ret)
+ return err
+ }
+
+ callback := template.JSEscapeString(r.Callback)
+ if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
+ return err
+ }
+
+ if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
+ return err
+ }
+
+ if _, err = w.Write(ret); err != nil {
+ return err
+ }
+
+ if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WriteContentType (JsonpJSON) writes Javascript ContentType.
+func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, jsonpContentType)
+}
+
+// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
+func (r AsciiJSON) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+ ret, err := json.API.Marshal(r.Data)
+ if err != nil {
+ return err
+ }
+
+ var buffer bytes.Buffer
+ escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences
+
+ for _, r := range bytesconv.BytesToString(ret) {
+ if r > unicode.MaxASCII {
+ escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf
+ buffer.Write(escapeBuf)
+ } else {
+ buffer.WriteByte(byte(r))
+ }
+ }
+
+ _, err = w.Write(buffer.Bytes())
+ return err
+}
+
+// WriteContentType (AsciiJSON) writes JSON ContentType.
+func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, jsonASCIIContentType)
+}
+
+// Render (PureJSON) writes custom ContentType and encodes the given interface object.
+func (r PureJSON) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+ encoder := json.API.NewEncoder(w)
+ encoder.SetEscapeHTML(false)
+ return encoder.Encode(r.Data)
+}
+
+// WriteContentType (PureJSON) writes custom ContentType.
+func (r PureJSON) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, jsonContentType)
+}
+
+
+
// Copyright 2017 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.
+
+//go:build !nomsgpack
+
+package render
+
+import (
+ "net/http"
+
+ "github.com/ugorji/go/codec"
+)
+
+// Check interface implemented here to support go build tag nomsgpack.
+// See: https://github.com/gin-gonic/gin/pull/1852/
+var (
+ _ Render = MsgPack{}
+)
+
+// MsgPack contains the given interface object.
+type MsgPack struct {
+ Data any
+}
+
+var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
+
+// WriteContentType (MsgPack) writes MsgPack ContentType.
+func (r MsgPack) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, msgpackContentType)
+}
+
+// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
+func (r MsgPack) Render(w http.ResponseWriter) error {
+ return WriteMsgPack(w, r.Data)
+}
+
+// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
+func WriteMsgPack(w http.ResponseWriter, obj any) error {
+ writeContentType(w, msgpackContentType)
+ var mh codec.MsgpackHandle
+ return codec.NewEncoder(w, &mh).Encode(obj)
+}
+
+
+
// Copyright 2018 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package render
+
+import (
+ "net/http"
+
+ "google.golang.org/protobuf/proto"
+)
+
+// ProtoBuf contains the given interface object.
+type ProtoBuf struct {
+ Data any
+}
+
+var protobufContentType = []string{"application/x-protobuf"}
+
+// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType.
+func (r ProtoBuf) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+
+ bytes, err := proto.Marshal(r.Data.(proto.Message))
+ if err != nil {
+ return err
+ }
+
+ _, err = w.Write(bytes)
+ return err
+}
+
+// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
+func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, protobufContentType)
+}
+
+
+
// Copyright 2018 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package render
+
+import (
+ "io"
+ "net/http"
+ "strconv"
+)
+
+// Reader contains the IO reader and its length, and custom ContentType and other headers.
+type Reader struct {
+ ContentType string
+ ContentLength int64
+ Reader io.Reader
+ Headers map[string]string
+}
+
+// Render (Reader) writes data with custom ContentType and headers.
+func (r Reader) Render(w http.ResponseWriter) (err error) {
+ r.WriteContentType(w)
+ if r.ContentLength >= 0 {
+ if r.Headers == nil {
+ r.Headers = map[string]string{}
+ }
+ r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
+ }
+ r.writeHeaders(w, r.Headers)
+ _, err = io.Copy(w, r.Reader)
+ return
+}
+
+// WriteContentType (Reader) writes custom ContentType.
+func (r Reader) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, []string{r.ContentType})
+}
+
+// writeHeaders writes custom Header.
+func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
+ header := w.Header()
+ for k, v := range headers {
+ if header.Get(k) == "" {
+ header.Set(k, v)
+ }
+ }
+}
+
+
+
// 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 render
+
+import (
+ "fmt"
+ "net/http"
+)
+
+// Redirect contains the http request reference and redirects status code and location.
+type Redirect struct {
+ Code int
+ Request *http.Request
+ Location string
+}
+
+// Render (Redirect) redirects the http request to new location and writes redirect response.
+func (r Redirect) Render(w http.ResponseWriter) error {
+ if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
+ panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
+ }
+ http.Redirect(w, r.Request, r.Location, r.Code)
+ return nil
+}
+
+// WriteContentType (Redirect) don't write any ContentType.
+func (r Redirect) WriteContentType(http.ResponseWriter) {}
+
+
+
// 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 render
+
+import "net/http"
+
+// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
+type Render interface {
+ // Render writes data with custom ContentType.
+ Render(http.ResponseWriter) error
+ // WriteContentType writes custom ContentType.
+ WriteContentType(w http.ResponseWriter)
+}
+
+var (
+ _ Render = (*JSON)(nil)
+ _ Render = (*IndentedJSON)(nil)
+ _ Render = (*SecureJSON)(nil)
+ _ Render = (*JsonpJSON)(nil)
+ _ Render = (*XML)(nil)
+ _ Render = (*String)(nil)
+ _ Render = (*Redirect)(nil)
+ _ Render = (*Data)(nil)
+ _ Render = (*HTML)(nil)
+ _ HTMLRender = (*HTMLDebug)(nil)
+ _ HTMLRender = (*HTMLProduction)(nil)
+ _ Render = (*YAML)(nil)
+ _ Render = (*Reader)(nil)
+ _ Render = (*AsciiJSON)(nil)
+ _ Render = (*ProtoBuf)(nil)
+ _ Render = (*TOML)(nil)
+)
+
+func writeContentType(w http.ResponseWriter, value []string) {
+ header := w.Header()
+ if val := header["Content-Type"]; len(val) == 0 {
+ header["Content-Type"] = value
+ }
+}
+
+
+
// 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 render
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gin-gonic/gin/internal/bytesconv"
+)
+
+// String contains the given interface object slice and its format.
+type String struct {
+ Format string
+ Data []any
+}
+
+var plainContentType = []string{"text/plain; charset=utf-8"}
+
+// Render (String) writes data with custom ContentType.
+func (r String) Render(w http.ResponseWriter) error {
+ return WriteString(w, r.Format, r.Data)
+}
+
+// WriteContentType (String) writes Plain ContentType.
+func (r String) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, plainContentType)
+}
+
+// WriteString writes data according to its format and write custom ContentType.
+func WriteString(w http.ResponseWriter, format string, data []any) (err error) {
+ writeContentType(w, plainContentType)
+ if len(data) > 0 {
+ _, err = fmt.Fprintf(w, format, data...)
+ return
+ }
+ _, err = w.Write(bytesconv.StringToBytes(format))
+ return
+}
+
+
+
// Copyright 2022 Gin Core Team. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package render
+
+import (
+ "net/http"
+
+ "github.com/pelletier/go-toml/v2"
+)
+
+// TOML contains the given interface object.
+type TOML struct {
+ Data any
+}
+
+var TOMLContentType = []string{"application/toml; charset=utf-8"}
+
+// Render (TOML) marshals the given interface object and writes data with custom ContentType.
+func (r TOML) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+
+ bytes, err := toml.Marshal(r.Data)
+ if err != nil {
+ return err
+ }
+
+ _, err = w.Write(bytes)
+ return err
+}
+
+// WriteContentType (TOML) writes TOML ContentType for response.
+func (r TOML) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, TOMLContentType)
+}
+
+
+
// 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 render
+
+import (
+ "encoding/xml"
+ "net/http"
+)
+
+// XML contains the given interface object.
+type XML struct {
+ Data any
+}
+
+var xmlContentType = []string{"application/xml; charset=utf-8"}
+
+// Render (XML) encodes the given interface object and writes data with custom ContentType.
+func (r XML) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+ return xml.NewEncoder(w).Encode(r.Data)
+}
+
+// WriteContentType (XML) writes XML ContentType for response.
+func (r XML) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, xmlContentType)
+}
+
+
+
// 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 render
+
+import (
+ "net/http"
+
+ "github.com/goccy/go-yaml"
+)
+
+// YAML contains the given interface object.
+type YAML struct {
+ Data any
+}
+
+var yamlContentType = []string{"application/yaml; charset=utf-8"}
+
+// Render (YAML) marshals the given interface object and writes data with custom ContentType.
+func (r YAML) Render(w http.ResponseWriter) error {
+ r.WriteContentType(w)
+
+ bytes, err := yaml.Marshal(r.Data)
+ if err != nil {
+ return err
+ }
+
+ _, err = w.Write(bytes)
+ return err
+}
+
+// WriteContentType (YAML) writes YAML ContentType for response.
+func (r YAML) WriteContentType(w http.ResponseWriter) {
+ writeContentType(w, yamlContentType)
+}
+
+
+
// 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 (
+ "bufio"
+ "io"
+ "net"
+ "net/http"
+)
+
+const (
+ noWritten = -1
+ defaultStatus = http.StatusOK
+)
+
+// ResponseWriter ...
+type ResponseWriter interface {
+ http.ResponseWriter
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+
+ // Status returns the HTTP response status code of the current request.
+ Status() int
+
+ // Size returns the number of bytes already written into the response http body.
+ // See Written()
+ Size() int
+
+ // WriteString writes the string into the response body.
+ WriteString(string) (int, error)
+
+ // Written returns true if the response body was already written.
+ Written() bool
+
+ // WriteHeaderNow forces to write the http header (status code + headers).
+ WriteHeaderNow()
+
+ // Pusher get the http.Pusher for server push
+ Pusher() http.Pusher
+}
+
+type responseWriter struct {
+ http.ResponseWriter
+ size int
+ status int
+}
+
+var _ ResponseWriter = (*responseWriter)(nil)
+
+func (w *responseWriter) Unwrap() http.ResponseWriter {
+ return w.ResponseWriter
+}
+
+func (w *responseWriter) reset(writer http.ResponseWriter) {
+ w.ResponseWriter = writer
+ w.size = noWritten
+ w.status = defaultStatus
+}
+
+func (w *responseWriter) WriteHeader(code int) {
+ if code > 0 && w.status != code {
+ if w.Written() {
+ debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
+ return
+ }
+ w.status = code
+ }
+}
+
+func (w *responseWriter) WriteHeaderNow() {
+ if !w.Written() {
+ w.size = 0
+ w.ResponseWriter.WriteHeader(w.status)
+ }
+}
+
+func (w *responseWriter) Write(data []byte) (n int, err error) {
+ w.WriteHeaderNow()
+ n, err = w.ResponseWriter.Write(data)
+ w.size += n
+ return
+}
+
+func (w *responseWriter) WriteString(s string) (n int, err error) {
+ w.WriteHeaderNow()
+ n, err = io.WriteString(w.ResponseWriter, s)
+ w.size += n
+ return
+}
+
+func (w *responseWriter) Status() int {
+ return w.status
+}
+
+func (w *responseWriter) Size() int {
+ return w.size
+}
+
+func (w *responseWriter) Written() bool {
+ return w.size != noWritten
+}
+
+// Hijack implements the http.Hijacker interface.
+func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ if w.size < 0 {
+ w.size = 0
+ }
+ return w.ResponseWriter.(http.Hijacker).Hijack()
+}
+
+// CloseNotify implements the http.CloseNotifier interface.
+func (w *responseWriter) CloseNotify() <-chan bool {
+ return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
+}
+
+// Flush implements the http.Flusher interface.
+func (w *responseWriter) Flush() {
+ w.WriteHeaderNow()
+ w.ResponseWriter.(http.Flusher).Flush()
+}
+
+func (w *responseWriter) Pusher() (pusher http.Pusher) {
+ if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
+ return pusher
+ }
+ return nil
+}
+
+
+
// 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 (
+ "net/http"
+ "path"
+ "regexp"
+ "strings"
+)
+
+var (
+ // regEnLetter matches english letters for http method name
+ regEnLetter = regexp.MustCompile("^[A-Z]+$")
+
+ // anyMethods for RouterGroup Any method
+ anyMethods = []string{
+ http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
+ http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
+ http.MethodTrace,
+ }
+)
+
+// IRouter defines all router handle interface includes single and group router.
+type IRouter interface {
+ IRoutes
+ Group(string, ...HandlerFunc) *RouterGroup
+}
+
+// IRoutes defines all router handle interface.
+type IRoutes interface {
+ Use(...HandlerFunc) IRoutes
+
+ Handle(string, string, ...HandlerFunc) IRoutes
+ Any(string, ...HandlerFunc) IRoutes
+ GET(string, ...HandlerFunc) IRoutes
+ POST(string, ...HandlerFunc) IRoutes
+ DELETE(string, ...HandlerFunc) IRoutes
+ PATCH(string, ...HandlerFunc) IRoutes
+ PUT(string, ...HandlerFunc) IRoutes
+ OPTIONS(string, ...HandlerFunc) IRoutes
+ HEAD(string, ...HandlerFunc) IRoutes
+ Match([]string, string, ...HandlerFunc) IRoutes
+
+ StaticFile(string, string) IRoutes
+ StaticFileFS(string, string, http.FileSystem) IRoutes
+ Static(string, string) IRoutes
+ StaticFS(string, http.FileSystem) IRoutes
+}
+
+// RouterGroup is used internally to configure router, a RouterGroup is associated with
+// a prefix and an array of handlers (middleware).
+type RouterGroup struct {
+ Handlers HandlersChain
+ basePath string
+ engine *Engine
+ root bool
+}
+
+var _ IRouter = (*RouterGroup)(nil)
+
+// Use adds middleware to the group, see example code in GitHub.
+func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
+ group.Handlers = append(group.Handlers, middleware...)
+ return group.returnObj()
+}
+
+// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
+// For example, all the routes that use a common middleware for authorization could be grouped.
+func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
+ return &RouterGroup{
+ Handlers: group.combineHandlers(handlers),
+ basePath: group.calculateAbsolutePath(relativePath),
+ engine: group.engine,
+ }
+}
+
+// BasePath returns the base path of router group.
+// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api".
+func (group *RouterGroup) BasePath() string {
+ return group.basePath
+}
+
+func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
+ absolutePath := group.calculateAbsolutePath(relativePath)
+ handlers = group.combineHandlers(handlers)
+ group.engine.addRoute(httpMethod, absolutePath, handlers)
+ return group.returnObj()
+}
+
+// Handle registers a new request handle and middleware with the given path and method.
+// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
+// See the example code in GitHub.
+//
+// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
+// functions can be used.
+//
+// This function is intended for bulk loading and to allow the usage of less
+// frequently used, non-standardized or custom methods (e.g. for internal
+// communication with a proxy).
+func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
+ if matched := regEnLetter.MatchString(httpMethod); !matched {
+ panic("http method " + httpMethod + " is not valid")
+ }
+ return group.handle(httpMethod, relativePath, handlers)
+}
+
+// POST is a shortcut for router.Handle("POST", path, handlers).
+func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodPost, relativePath, handlers)
+}
+
+// GET is a shortcut for router.Handle("GET", path, handlers).
+func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodGet, relativePath, handlers)
+}
+
+// DELETE is a shortcut for router.Handle("DELETE", path, handlers).
+func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodDelete, relativePath, handlers)
+}
+
+// PATCH is a shortcut for router.Handle("PATCH", path, handlers).
+func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodPatch, relativePath, handlers)
+}
+
+// PUT is a shortcut for router.Handle("PUT", path, handlers).
+func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodPut, relativePath, handlers)
+}
+
+// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers).
+func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodOptions, relativePath, handlers)
+}
+
+// HEAD is a shortcut for router.Handle("HEAD", path, handlers).
+func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
+ return group.handle(http.MethodHead, relativePath, handlers)
+}
+
+// Any registers a route that matches all the HTTP methods.
+// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
+func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
+ for _, method := range anyMethods {
+ group.handle(method, relativePath, handlers)
+ }
+
+ return group.returnObj()
+}
+
+// Match registers a route that matches the specified methods that you declared.
+func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
+ for _, method := range methods {
+ group.handle(method, relativePath, handlers)
+ }
+
+ return group.returnObj()
+}
+
+// StaticFile registers a single route in order to serve a single file of the local filesystem.
+// router.StaticFile("favicon.ico", "./resources/favicon.ico")
+func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
+ return group.staticFileHandler(relativePath, func(c *Context) {
+ c.File(filepath)
+ })
+}
+
+// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead..
+// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false})
+// Gin by default uses: gin.Dir()
+func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {
+ return group.staticFileHandler(relativePath, func(c *Context) {
+ c.FileFromFS(filepath, fs)
+ })
+}
+
+func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {
+ if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
+ panic("URL parameters can not be used when serving a static file")
+ }
+ group.GET(relativePath, handler)
+ group.HEAD(relativePath, handler)
+ return group.returnObj()
+}
+
+// Static serves files from the given file system root.
+// Internally a http.FileServer is used, therefore http.NotFound is used instead
+// of the Router's NotFound handler.
+// To use the operating system's file system implementation,
+// use :
+//
+// router.Static("/static", "/var/www")
+func (group *RouterGroup) Static(relativePath, root string) IRoutes {
+ return group.StaticFS(relativePath, Dir(root, false))
+}
+
+// StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.
+// Gin by default uses: gin.Dir()
+func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
+ if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
+ panic("URL parameters can not be used when serving a static folder")
+ }
+ handler := group.createStaticHandler(relativePath, fs)
+ urlPattern := path.Join(relativePath, "/*filepath")
+
+ // Register GET and HEAD handlers
+ group.GET(urlPattern, handler)
+ group.HEAD(urlPattern, handler)
+ return group.returnObj()
+}
+
+func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
+ absolutePath := group.calculateAbsolutePath(relativePath)
+ fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
+
+ return func(c *Context) {
+ if _, noListing := fs.(*OnlyFilesFS); noListing {
+ c.Writer.WriteHeader(http.StatusNotFound)
+ }
+
+ file := c.Param("filepath")
+ // Check if file exists and/or if we have permission to access it
+ f, err := fs.Open(file)
+ if err != nil {
+ c.Writer.WriteHeader(http.StatusNotFound)
+ c.handlers = group.engine.noRoute
+ // Reset index
+ c.index = -1
+ return
+ }
+ f.Close()
+
+ fileServer.ServeHTTP(c.Writer, c.Request)
+ }
+}
+
+func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
+ finalSize := len(group.Handlers) + len(handlers)
+ assert1(finalSize < int(abortIndex), "too many handlers")
+ mergedHandlers := make(HandlersChain, finalSize)
+ copy(mergedHandlers, group.Handlers)
+ copy(mergedHandlers[len(group.Handlers):], handlers)
+ return mergedHandlers
+}
+
+func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
+ return joinPaths(group.basePath, relativePath)
+}
+
+func (group *RouterGroup) returnObj() IRoutes {
+ if group.root {
+ return group.engine
+ }
+ return group
+}
+
+
+
// Copyright 2017 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 "net/http"
+
+// CreateTestContext returns a fresh engine and context for testing purposes
+func CreateTestContext(w http.ResponseWriter) (c *Context, r *Engine) {
+ r = New()
+ c = r.allocateContext(0)
+ c.reset()
+ c.writermem.reset(w)
+ return
+}
+
+// CreateTestContextOnly returns a fresh context base on the engine for testing purposes
+func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) {
+ c = r.allocateContext(r.maxParams)
+ c.reset()
+ c.writermem.reset(w)
+ return
+}
+
+
+
// Copyright 2013 Julien Schmidt. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be found
+// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE
+
+package gin
+
+import (
+ "bytes"
+ "net/url"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/gin-gonic/gin/internal/bytesconv"
+)
+
+var (
+ strColon = []byte(":")
+ strStar = []byte("*")
+ strSlash = []byte("/")
+)
+
+// Param is a single URL parameter, consisting of a key and a value.
+type Param struct {
+ Key string
+ Value string
+}
+
+// Params is a Param-slice, as returned by the router.
+// The slice is ordered, the first URL parameter is also the first slice value.
+// It is therefore safe to read values by the index.
+type Params []Param
+
+// Get returns the value of the first Param which key matches the given name and a boolean true.
+// If no matching Param is found, an empty string is returned and a boolean false .
+func (ps Params) Get(name string) (string, bool) {
+ for _, entry := range ps {
+ if entry.Key == name {
+ return entry.Value, true
+ }
+ }
+ return "", false
+}
+
+// ByName returns the value of the first Param which key matches the given name.
+// If no matching Param is found, an empty string is returned.
+func (ps Params) ByName(name string) (va string) {
+ va, _ = ps.Get(name)
+ return
+}
+
+type methodTree struct {
+ method string
+ root *node
+}
+
+type methodTrees []methodTree
+
+func (trees methodTrees) get(method string) *node {
+ for _, tree := range trees {
+ if tree.method == method {
+ return tree.root
+ }
+ }
+ return nil
+}
+
+func longestCommonPrefix(a, b string) int {
+ i := 0
+ max_ := min(len(a), len(b))
+ for i < max_ && a[i] == b[i] {
+ i++
+ }
+ return i
+}
+
+// addChild will add a child node, keeping wildcardChild at the end
+func (n *node) addChild(child *node) {
+ if n.wildChild && len(n.children) > 0 {
+ wildcardChild := n.children[len(n.children)-1]
+ n.children = append(n.children[:len(n.children)-1], child, wildcardChild)
+ } else {
+ n.children = append(n.children, child)
+ }
+}
+
+func countParams(path string) uint16 {
+ var n uint16
+ s := bytesconv.StringToBytes(path)
+ n += uint16(bytes.Count(s, strColon))
+ n += uint16(bytes.Count(s, strStar))
+ return n
+}
+
+func countSections(path string) uint16 {
+ s := bytesconv.StringToBytes(path)
+ return uint16(bytes.Count(s, strSlash))
+}
+
+type nodeType uint8
+
+const (
+ static nodeType = iota
+ root
+ param
+ catchAll
+)
+
+type node struct {
+ path string
+ indices string
+ wildChild bool
+ nType nodeType
+ priority uint32
+ children []*node // child nodes, at most 1 :param style node at the end of the array
+ handlers HandlersChain
+ fullPath string
+}
+
+// Increments priority of the given child and reorders if necessary
+func (n *node) incrementChildPrio(pos int) int {
+ cs := n.children
+ cs[pos].priority++
+ prio := cs[pos].priority
+
+ // Adjust position (move to front)
+ newPos := pos
+ for ; newPos > 0 && cs[newPos-1].priority < prio; newPos-- {
+ // Swap node positions
+ cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
+ }
+
+ // Build new index char string
+ if newPos != pos {
+ n.indices = n.indices[:newPos] + // Unchanged prefix, might be empty
+ n.indices[pos:pos+1] + // The index char we move
+ n.indices[newPos:pos] + n.indices[pos+1:] // Rest without char at 'pos'
+ }
+
+ return newPos
+}
+
+// addRoute adds a node with the given handle to the path.
+// Not concurrency-safe!
+func (n *node) addRoute(path string, handlers HandlersChain) {
+ fullPath := path
+ n.priority++
+
+ // Empty tree
+ if len(n.path) == 0 && len(n.children) == 0 {
+ n.insertChild(path, fullPath, handlers)
+ n.nType = root
+ return
+ }
+
+ parentFullPathIndex := 0
+
+walk:
+ for {
+ // Find the longest common prefix.
+ // This also implies that the common prefix contains no ':' or '*'
+ // since the existing key can't contain those chars.
+ i := longestCommonPrefix(path, n.path)
+
+ // Split edge
+ if i < len(n.path) {
+ child := node{
+ path: n.path[i:],
+ wildChild: n.wildChild,
+ nType: static,
+ indices: n.indices,
+ children: n.children,
+ handlers: n.handlers,
+ priority: n.priority - 1,
+ fullPath: n.fullPath,
+ }
+
+ n.children = []*node{&child}
+ // []byte for proper unicode char conversion, see #65
+ n.indices = bytesconv.BytesToString([]byte{n.path[i]})
+ n.path = path[:i]
+ n.handlers = nil
+ n.wildChild = false
+ n.fullPath = fullPath[:parentFullPathIndex+i]
+ }
+
+ // Make new node a child of this node
+ if i < len(path) {
+ path = path[i:]
+ c := path[0]
+
+ // '/' after param
+ if n.nType == param && c == '/' && len(n.children) == 1 {
+ parentFullPathIndex += len(n.path)
+ n = n.children[0]
+ n.priority++
+ continue walk
+ }
+
+ // Check if a child with the next path byte exists
+ for i, max_ := 0, len(n.indices); i < max_; i++ {
+ if c == n.indices[i] {
+ parentFullPathIndex += len(n.path)
+ i = n.incrementChildPrio(i)
+ n = n.children[i]
+ continue walk
+ }
+ }
+
+ // Otherwise insert it
+ if c != ':' && c != '*' && n.nType != catchAll {
+ // []byte for proper unicode char conversion, see #65
+ n.indices += bytesconv.BytesToString([]byte{c})
+ child := &node{
+ fullPath: fullPath,
+ }
+ n.addChild(child)
+ n.incrementChildPrio(len(n.indices) - 1)
+ n = child
+ } else if n.wildChild {
+ // inserting a wildcard node, need to check if it conflicts with the existing wildcard
+ n = n.children[len(n.children)-1]
+ n.priority++
+
+ // Check if the wildcard matches
+ if len(path) >= len(n.path) && n.path == path[:len(n.path)] &&
+ // Adding a child to a catchAll is not possible
+ n.nType != catchAll &&
+ // Check for longer wildcard, e.g. :name and :names
+ (len(n.path) >= len(path) || path[len(n.path)] == '/') {
+ continue walk
+ }
+
+ // Wildcard conflict
+ pathSeg := path
+ if n.nType != catchAll {
+ pathSeg, _, _ = strings.Cut(pathSeg, "/")
+ }
+ prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
+ panic("'" + pathSeg +
+ "' in new path '" + fullPath +
+ "' conflicts with existing wildcard '" + n.path +
+ "' in existing prefix '" + prefix +
+ "'")
+ }
+
+ n.insertChild(path, fullPath, handlers)
+ return
+ }
+
+ // Otherwise add handle to current node
+ if n.handlers != nil {
+ panic("handlers are already registered for path '" + fullPath + "'")
+ }
+ n.handlers = handlers
+ n.fullPath = fullPath
+ return
+ }
+}
+
+// Search for a wildcard segment and check the name for invalid characters.
+// Returns -1 as index, if no wildcard was found.
+func findWildcard(path string) (wildcard string, i int, valid bool) {
+ // Find start
+ escapeColon := false
+ for start, c := range []byte(path) {
+ if escapeColon {
+ escapeColon = false
+ if c == ':' {
+ continue
+ }
+ panic("invalid escape string in path '" + path + "'")
+ }
+ if c == '\\' {
+ escapeColon = true
+ continue
+ }
+ // A wildcard starts with ':' (param) or '*' (catch-all)
+ if c != ':' && c != '*' {
+ continue
+ }
+
+ // Find end and check for invalid characters
+ valid = true
+ for end, c := range []byte(path[start+1:]) {
+ switch c {
+ case '/':
+ return path[start : start+1+end], start, valid
+ case ':', '*':
+ valid = false
+ }
+ }
+ return path[start:], start, valid
+ }
+ return "", -1, false
+}
+
+func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) {
+ for {
+ // Find prefix until first wildcard
+ wildcard, i, valid := findWildcard(path)
+ if i < 0 { // No wildcard found
+ break
+ }
+
+ // The wildcard name must only contain one ':' or '*' character
+ if !valid {
+ panic("only one wildcard per path segment is allowed, has: '" +
+ wildcard + "' in path '" + fullPath + "'")
+ }
+
+ // check if the wildcard has a name
+ if len(wildcard) < 2 {
+ panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")
+ }
+
+ if wildcard[0] == ':' { // param
+ if i > 0 {
+ // Insert prefix before the current wildcard
+ n.path = path[:i]
+ path = path[i:]
+ }
+
+ child := &node{
+ nType: param,
+ path: wildcard,
+ fullPath: fullPath,
+ }
+ n.addChild(child)
+ n.wildChild = true
+ n = child
+ n.priority++
+
+ // if the path doesn't end with the wildcard, then there
+ // will be another subpath starting with '/'
+ if len(wildcard) < len(path) {
+ path = path[len(wildcard):]
+
+ child := &node{
+ priority: 1,
+ fullPath: fullPath,
+ }
+ n.addChild(child)
+ n = child
+ continue
+ }
+
+ // Otherwise we're done. Insert the handle in the new leaf
+ n.handlers = handlers
+ return
+ }
+
+ // catchAll
+ if i+len(wildcard) != len(path) {
+ panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")
+ }
+
+ if len(n.path) > 0 && n.path[len(n.path)-1] == '/' {
+ pathSeg := ""
+ if len(n.children) != 0 {
+ pathSeg, _, _ = strings.Cut(n.children[0].path, "/")
+ }
+ panic("catch-all wildcard '" + path +
+ "' in new path '" + fullPath +
+ "' conflicts with existing path segment '" + pathSeg +
+ "' in existing prefix '" + n.path + pathSeg +
+ "'")
+ }
+
+ // currently fixed width 1 for '/'
+ i--
+ if i < 0 || path[i] != '/' {
+ panic("no / before catch-all in path '" + fullPath + "'")
+ }
+
+ n.path = path[:i]
+
+ // First node: catchAll node with empty path
+ child := &node{
+ wildChild: true,
+ nType: catchAll,
+ fullPath: fullPath,
+ }
+
+ n.addChild(child)
+ n.indices = string('/')
+ n = child
+ n.priority++
+
+ // second node: node holding the variable
+ child = &node{
+ path: path[i:],
+ nType: catchAll,
+ handlers: handlers,
+ priority: 1,
+ fullPath: fullPath,
+ }
+ n.children = []*node{child}
+
+ return
+ }
+
+ // If no wildcard was found, simply insert the path and handle
+ n.path = path
+ n.handlers = handlers
+ n.fullPath = fullPath
+}
+
+// nodeValue holds return values of (*Node).getValue method
+type nodeValue struct {
+ handlers HandlersChain
+ params *Params
+ tsr bool
+ fullPath string
+}
+
+type skippedNode struct {
+ path string
+ node *node
+ paramsCount int16
+}
+
+// Returns the handle registered with the given path (key). The values of
+// wildcards are saved to a map.
+// If no handle can be found, a TSR (trailing slash redirect) recommendation is
+// made if a handle exists with an extra (without the) trailing slash for the
+// given path.
+func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
+ var globalParamsCount int16
+
+walk: // Outer loop for walking the tree
+ for {
+ prefix := n.path
+ if len(path) > len(prefix) {
+ if path[:len(prefix)] == prefix {
+ path = path[len(prefix):]
+
+ // Try all the non-wildcard children first by matching the indices
+ idxc := path[0]
+ for i, c := range []byte(n.indices) {
+ if c == idxc {
+ // strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
+ if n.wildChild {
+ index := len(*skippedNodes)
+ *skippedNodes = (*skippedNodes)[:index+1]
+ (*skippedNodes)[index] = skippedNode{
+ path: prefix + path,
+ node: &node{
+ path: n.path,
+ wildChild: n.wildChild,
+ nType: n.nType,
+ priority: n.priority,
+ children: n.children,
+ handlers: n.handlers,
+ fullPath: n.fullPath,
+ },
+ paramsCount: globalParamsCount,
+ }
+ }
+
+ n = n.children[i]
+ continue walk
+ }
+ }
+
+ if !n.wildChild {
+ // If the path at the end of the loop is not equal to '/' and the current node has no child nodes
+ // the current node needs to roll back to last valid skippedNode
+ if path != "/" {
+ for length := len(*skippedNodes); length > 0; length-- {
+ skippedNode := (*skippedNodes)[length-1]
+ *skippedNodes = (*skippedNodes)[:length-1]
+ if strings.HasSuffix(skippedNode.path, path) {
+ path = skippedNode.path
+ n = skippedNode.node
+ if value.params != nil {
+ *value.params = (*value.params)[:skippedNode.paramsCount]
+ }
+ globalParamsCount = skippedNode.paramsCount
+ continue walk
+ }
+ }
+ }
+
+ // Nothing found.
+ // We can recommend to redirect to the same URL without a
+ // trailing slash if a leaf exists for that path.
+ value.tsr = path == "/" && n.handlers != nil
+ return value
+ }
+
+ // Handle wildcard child, which is always at the end of the array
+ n = n.children[len(n.children)-1]
+ globalParamsCount++
+
+ switch n.nType {
+ case param:
+ // fix truncate the parameter
+ // tree_test.go line: 204
+
+ // Find param end (either '/' or path end)
+ end := 0
+ for end < len(path) && path[end] != '/' {
+ end++
+ }
+
+ // Save param value
+ if params != nil {
+ // Preallocate capacity if necessary
+ if cap(*params) < int(globalParamsCount) {
+ newParams := make(Params, len(*params), globalParamsCount)
+ copy(newParams, *params)
+ *params = newParams
+ }
+
+ if value.params == nil {
+ value.params = params
+ }
+ // Expand slice within preallocated capacity
+ i := len(*value.params)
+ *value.params = (*value.params)[:i+1]
+ val := path[:end]
+ if unescape {
+ if v, err := url.QueryUnescape(val); err == nil {
+ val = v
+ }
+ }
+ (*value.params)[i] = Param{
+ Key: n.path[1:],
+ Value: val,
+ }
+ }
+
+ // we need to go deeper!
+ if end < len(path) {
+ if len(n.children) > 0 {
+ path = path[end:]
+ n = n.children[0]
+ continue walk
+ }
+
+ // ... but we can't
+ value.tsr = len(path) == end+1
+ return value
+ }
+
+ if value.handlers = n.handlers; value.handlers != nil {
+ value.fullPath = n.fullPath
+ return value
+ }
+ if len(n.children) == 1 {
+ // No handle found. Check if a handle for this path + a
+ // trailing slash exists for TSR recommendation
+ n = n.children[0]
+ value.tsr = (n.path == "/" && n.handlers != nil) || (n.path == "" && n.indices == "/")
+ }
+ return value
+
+ case catchAll:
+ // Save param value
+ if params != nil {
+ // Preallocate capacity if necessary
+ if cap(*params) < int(globalParamsCount) {
+ newParams := make(Params, len(*params), globalParamsCount)
+ copy(newParams, *params)
+ *params = newParams
+ }
+
+ if value.params == nil {
+ value.params = params
+ }
+ // Expand slice within preallocated capacity
+ i := len(*value.params)
+ *value.params = (*value.params)[:i+1]
+ val := path
+ if unescape {
+ if v, err := url.QueryUnescape(path); err == nil {
+ val = v
+ }
+ }
+ (*value.params)[i] = Param{
+ Key: n.path[2:],
+ Value: val,
+ }
+ }
+
+ value.handlers = n.handlers
+ value.fullPath = n.fullPath
+ return value
+
+ default:
+ panic("invalid node type")
+ }
+ }
+ }
+
+ if path == prefix {
+ // If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
+ // the current node needs to roll back to last valid skippedNode
+ if n.handlers == nil && path != "/" {
+ for length := len(*skippedNodes); length > 0; length-- {
+ skippedNode := (*skippedNodes)[length-1]
+ *skippedNodes = (*skippedNodes)[:length-1]
+ if strings.HasSuffix(skippedNode.path, path) {
+ path = skippedNode.path
+ n = skippedNode.node
+ if value.params != nil {
+ *value.params = (*value.params)[:skippedNode.paramsCount]
+ }
+ globalParamsCount = skippedNode.paramsCount
+ continue walk
+ }
+ }
+ // n = latestNode.children[len(latestNode.children)-1]
+ }
+ // We should have reached the node containing the handle.
+ // Check if this node has a handle registered.
+ if value.handlers = n.handlers; value.handlers != nil {
+ value.fullPath = n.fullPath
+ return value
+ }
+
+ // If there is no handle for this route, but this route has a
+ // wildcard child, there must be a handle for this path with an
+ // additional trailing slash
+ if path == "/" && n.wildChild && n.nType != root {
+ value.tsr = true
+ return value
+ }
+
+ if path == "/" && n.nType == static {
+ value.tsr = true
+ return value
+ }
+
+ // No handle found. Check if a handle for this path + a
+ // trailing slash exists for trailing slash recommendation
+ for i, c := range []byte(n.indices) {
+ if c == '/' {
+ n = n.children[i]
+ value.tsr = (len(n.path) == 1 && n.handlers != nil) ||
+ (n.nType == catchAll && n.children[0].handlers != nil)
+ return value
+ }
+ }
+
+ return value
+ }
+
+ // Nothing found. We can recommend to redirect to the same URL with an
+ // extra trailing slash if a leaf exists for that path
+ value.tsr = path == "/" ||
+ (len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
+ path == prefix[:len(prefix)-1] && n.handlers != nil)
+
+ // roll back to last valid skippedNode
+ if !value.tsr && path != "/" {
+ for length := len(*skippedNodes); length > 0; length-- {
+ skippedNode := (*skippedNodes)[length-1]
+ *skippedNodes = (*skippedNodes)[:length-1]
+ if strings.HasSuffix(skippedNode.path, path) {
+ path = skippedNode.path
+ n = skippedNode.node
+ if value.params != nil {
+ *value.params = (*value.params)[:skippedNode.paramsCount]
+ }
+ globalParamsCount = skippedNode.paramsCount
+ continue walk
+ }
+ }
+ }
+
+ return value
+ }
+}
+
+// Makes a case-insensitive lookup of the given path and tries to find a handler.
+// It can optionally also fix trailing slashes.
+// It returns the case-corrected path and a bool indicating whether the lookup
+// was successful.
+func (n *node) findCaseInsensitivePath(path string, fixTrailingSlash bool) ([]byte, bool) {
+ const stackBufSize = 128
+
+ // Use a static sized buffer on the stack in the common case.
+ // If the path is too long, allocate a buffer on the heap instead.
+ buf := make([]byte, 0, stackBufSize)
+ if length := len(path) + 1; length > stackBufSize {
+ buf = make([]byte, 0, length)
+ }
+
+ ciPath := n.findCaseInsensitivePathRec(
+ path,
+ buf, // Preallocate enough memory for new path
+ [4]byte{}, // Empty rune buffer
+ fixTrailingSlash,
+ )
+
+ return ciPath, ciPath != nil
+}
+
+// Shift bytes in array by n bytes left
+func shiftNRuneBytes(rb [4]byte, n int) [4]byte {
+ switch n {
+ case 0:
+ return rb
+ case 1:
+ return [4]byte{rb[1], rb[2], rb[3], 0}
+ case 2:
+ return [4]byte{rb[2], rb[3]}
+ case 3:
+ return [4]byte{rb[3]}
+ default:
+ return [4]byte{}
+ }
+}
+
+// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
+func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte {
+ npLen := len(n.path)
+
+walk: // Outer loop for walking the tree
+ for len(path) >= npLen && (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) {
+ // Add common prefix to result
+ oldPath := path
+ path = path[npLen:]
+ ciPath = append(ciPath, n.path...)
+
+ if len(path) == 0 {
+ // We should have reached the node containing the handle.
+ // Check if this node has a handle registered.
+ if n.handlers != nil {
+ return ciPath
+ }
+
+ // No handle found.
+ // Try to fix the path by adding a trailing slash
+ if fixTrailingSlash {
+ for i, c := range []byte(n.indices) {
+ if c == '/' {
+ n = n.children[i]
+ if (len(n.path) == 1 && n.handlers != nil) ||
+ (n.nType == catchAll && n.children[0].handlers != nil) {
+ return append(ciPath, '/')
+ }
+ return nil
+ }
+ }
+ }
+ return nil
+ }
+
+ // If this node does not have a wildcard (param or catchAll) child,
+ // we can just look up the next child node and continue to walk down
+ // the tree
+ if !n.wildChild {
+ // Skip rune bytes already processed
+ rb = shiftNRuneBytes(rb, npLen)
+
+ if rb[0] != 0 {
+ // Old rune not finished
+ idxc := rb[0]
+ for i, c := range []byte(n.indices) {
+ if c == idxc {
+ // continue with child node
+ n = n.children[i]
+ npLen = len(n.path)
+ continue walk
+ }
+ }
+ } else {
+ // Process a new rune
+ var rv rune
+
+ // Find rune start.
+ // Runes are up to 4 byte long,
+ // -4 would definitely be another rune.
+ var off int
+ for max_ := min(npLen, 3); off < max_; off++ {
+ if i := npLen - off; utf8.RuneStart(oldPath[i]) {
+ // read rune from cached path
+ rv, _ = utf8.DecodeRuneInString(oldPath[i:])
+ break
+ }
+ }
+
+ // Calculate lowercase bytes of current rune
+ lo := unicode.ToLower(rv)
+ utf8.EncodeRune(rb[:], lo)
+
+ // Skip already processed bytes
+ rb = shiftNRuneBytes(rb, off)
+
+ idxc := rb[0]
+ for i, c := range []byte(n.indices) {
+ // Lowercase matches
+ if c == idxc {
+ // must use a recursive approach since both the
+ // uppercase byte and the lowercase byte might exist
+ // as an index
+ if out := n.children[i].findCaseInsensitivePathRec(
+ path, ciPath, rb, fixTrailingSlash,
+ ); out != nil {
+ return out
+ }
+ break
+ }
+ }
+
+ // If we found no match, the same for the uppercase rune,
+ // if it differs
+ if up := unicode.ToUpper(rv); up != lo {
+ utf8.EncodeRune(rb[:], up)
+ rb = shiftNRuneBytes(rb, off)
+
+ idxc := rb[0]
+ for i, c := range []byte(n.indices) {
+ // Uppercase matches
+ if c == idxc {
+ // Continue with child node
+ n = n.children[i]
+ npLen = len(n.path)
+ continue walk
+ }
+ }
+ }
+ }
+
+ // Nothing found. We can recommend to redirect to the same URL
+ // without a trailing slash if a leaf exists for that path
+ if fixTrailingSlash && path == "/" && n.handlers != nil {
+ return ciPath
+ }
+ return nil
+ }
+
+ n = n.children[0]
+ switch n.nType {
+ case param:
+ // Find param end (either '/' or path end)
+ end := 0
+ for end < len(path) && path[end] != '/' {
+ end++
+ }
+
+ // Add param value to case insensitive path
+ ciPath = append(ciPath, path[:end]...)
+
+ // We need to go deeper!
+ if end < len(path) {
+ if len(n.children) > 0 {
+ // Continue with child node
+ n = n.children[0]
+ npLen = len(n.path)
+ path = path[end:]
+ continue
+ }
+
+ // ... but we can't
+ if fixTrailingSlash && len(path) == end+1 {
+ return ciPath
+ }
+ return nil
+ }
+
+ if n.handlers != nil {
+ return ciPath
+ }
+
+ if fixTrailingSlash && len(n.children) == 1 {
+ // No handle found. Check if a handle for this path + a
+ // trailing slash exists
+ n = n.children[0]
+ if n.path == "/" && n.handlers != nil {
+ return append(ciPath, '/')
+ }
+ }
+
+ return nil
+
+ case catchAll:
+ return append(ciPath, path...)
+
+ default:
+ panic("invalid node type")
+ }
+ }
+
+ // Nothing found.
+ // Try to fix the path by adding / removing a trailing slash
+ if fixTrailingSlash {
+ if path == "/" {
+ return ciPath
+ }
+ if len(path)+1 == npLen && n.path[len(path)] == '/' &&
+ strings.EqualFold(path[1:], n.path[1:len(path)]) && n.handlers != nil {
+ return append(ciPath, n.path...)
+ }
+ }
+ return nil
+}
+
+
+
// 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 (
+ "encoding/xml"
+ "net/http"
+ "os"
+ "path"
+ "reflect"
+ "runtime"
+ "strings"
+ "unicode"
+)
+
+// BindKey indicates a default bind key.
+const BindKey = "_gin-gonic/gin/bindkey"
+
+// Bind is a helper function for given interface object and returns a Gin middleware.
+func Bind(val any) HandlerFunc {
+ value := reflect.ValueOf(val)
+ if value.Kind() == reflect.Ptr {
+ panic(`Bind struct can not be a pointer. Example:
+ Use: gin.Bind(Struct{}) instead of gin.Bind(&Struct{})
+`)
+ }
+ typ := value.Type()
+
+ return func(c *Context) {
+ obj := reflect.New(typ).Interface()
+ if c.Bind(obj) == nil {
+ c.Set(BindKey, obj)
+ }
+ }
+}
+
+// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.
+func WrapF(f http.HandlerFunc) HandlerFunc {
+ return func(c *Context) {
+ f(c.Writer, c.Request)
+ }
+}
+
+// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware.
+func WrapH(h http.Handler) HandlerFunc {
+ return func(c *Context) {
+ h.ServeHTTP(c.Writer, c.Request)
+ }
+}
+
+// H is a shortcut for map[string]any
+type H map[string]any
+
+// MarshalXML allows type H to be used with xml.Marshal.
+func (h H) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+ start.Name = xml.Name{
+ Space: "",
+ Local: "map",
+ }
+ if err := e.EncodeToken(start); err != nil {
+ return err
+ }
+ for key, value := range h {
+ elem := xml.StartElement{
+ Name: xml.Name{Space: "", Local: key},
+ Attr: []xml.Attr{},
+ }
+ if err := e.EncodeElement(value, elem); err != nil {
+ return err
+ }
+ }
+
+ return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+func assert1(guard bool, text string) {
+ if !guard {
+ panic(text)
+ }
+}
+
+func filterFlags(content string) string {
+ for i, char := range content {
+ if char == ' ' || char == ';' {
+ return content[:i]
+ }
+ }
+ return content
+}
+
+func chooseData(custom, wildcard any) any {
+ if custom != nil {
+ return custom
+ }
+ if wildcard != nil {
+ return wildcard
+ }
+ panic("negotiation config is invalid")
+}
+
+func parseAccept(acceptHeader string) []string {
+ parts := strings.Split(acceptHeader, ",")
+ out := make([]string, 0, len(parts))
+ for _, part := range parts {
+ if i := strings.IndexByte(part, ';'); i > 0 {
+ part = part[:i]
+ }
+ if part = strings.TrimSpace(part); part != "" {
+ out = append(out, part)
+ }
+ }
+ return out
+}
+
+func lastChar(str string) uint8 {
+ if str == "" {
+ panic("The length of the string can't be 0")
+ }
+ return str[len(str)-1]
+}
+
+func nameOfFunction(f any) string {
+ return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
+}
+
+func joinPaths(absolutePath, relativePath string) string {
+ if relativePath == "" {
+ return absolutePath
+ }
+
+ finalPath := path.Join(absolutePath, relativePath)
+ if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
+ return finalPath + "/"
+ }
+ return finalPath
+}
+
+func resolveAddress(addr []string) string {
+ switch len(addr) {
+ case 0:
+ if port := os.Getenv("PORT"); port != "" {
+ debugPrint("Environment variable PORT=\"%s\"", port)
+ return ":" + port
+ }
+ debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
+ return ":8080"
+ case 1:
+ return addr[0]
+ default:
+ panic("too many parameters")
+ }
+}
+
+// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters
+func isASCII(s string) bool {
+ for i := 0; i < len(s); i++ {
+ if s[i] > unicode.MaxASCII {
+ return false
+ }
+ }
+ return true
+}
+
+
+
+
+
+