gin/coverage.html
caplost 15ef1d4739 feat: add comprehensive tests for Context.Negotiate() method
Add test cases covering:
- JSON, HTML, XML, YAML, TOML content negotiation
- Accept header parsing with quality values
- Fallback mechanisms and data precedence
- Wildcard and partial matching
- Error handling for unsupported formats

All tests pass successfully.
2025-07-29 12:02:27 +08:00

7272 lines
304 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>gin: Go Coverage Report</title>
<style>
body {
background: black;
color: rgb(80, 80, 80);
}
body, pre, #legend span {
font-family: Menlo, monospace;
font-weight: bold;
}
#topbar {
background: black;
position: fixed;
top: 0; left: 0; right: 0;
height: 42px;
border-bottom: 1px solid rgb(80, 80, 80);
}
#content {
margin-top: 50px;
}
#nav, #legend {
float: left;
margin-left: 10px;
}
#legend {
margin-top: 12px;
}
#nav {
margin-top: 10px;
}
#legend span {
margin: 0 5px;
}
.cov0 { color: rgb(192, 0, 0) }
.cov1 { color: rgb(128, 128, 128) }
.cov2 { color: rgb(116, 140, 131) }
.cov3 { color: rgb(104, 152, 134) }
.cov4 { color: rgb(92, 164, 137) }
.cov5 { color: rgb(80, 176, 140) }
.cov6 { color: rgb(68, 188, 143) }
.cov7 { color: rgb(56, 200, 146) }
.cov8 { color: rgb(44, 212, 149) }
.cov9 { color: rgb(32, 224, 152) }
.cov10 { color: rgb(20, 236, 155) }
</style>
</head>
<body>
<div id="topbar">
<div id="nav">
<select id="files">
<option value="file0">github.com/gin-gonic/gin/auth.go (100.0%)</option>
<option value="file1">github.com/gin-gonic/gin/binding/binding.go (100.0%)</option>
<option value="file2">github.com/gin-gonic/gin/binding/default_validator.go (100.0%)</option>
<option value="file3">github.com/gin-gonic/gin/binding/form.go (100.0%)</option>
<option value="file4">github.com/gin-gonic/gin/binding/form_mapping.go (100.0%)</option>
<option value="file5">github.com/gin-gonic/gin/binding/header.go (100.0%)</option>
<option value="file6">github.com/gin-gonic/gin/binding/json.go (100.0%)</option>
<option value="file7">github.com/gin-gonic/gin/binding/msgpack.go (100.0%)</option>
<option value="file8">github.com/gin-gonic/gin/binding/multipart_form_mapping.go (100.0%)</option>
<option value="file9">github.com/gin-gonic/gin/binding/plain.go (95.0%)</option>
<option value="file10">github.com/gin-gonic/gin/binding/protobuf.go (100.0%)</option>
<option value="file11">github.com/gin-gonic/gin/binding/query.go (100.0%)</option>
<option value="file12">github.com/gin-gonic/gin/binding/toml.go (100.0%)</option>
<option value="file13">github.com/gin-gonic/gin/binding/uri.go (100.0%)</option>
<option value="file14">github.com/gin-gonic/gin/binding/xml.go (100.0%)</option>
<option value="file15">github.com/gin-gonic/gin/binding/yaml.go (100.0%)</option>
<option value="file16">github.com/gin-gonic/gin/codec/json/json.go (0.0%)</option>
<option value="file17">github.com/gin-gonic/gin/context.go (99.5%)</option>
<option value="file18">github.com/gin-gonic/gin/debug.go (97.1%)</option>
<option value="file19">github.com/gin-gonic/gin/deprecated.go (100.0%)</option>
<option value="file20">github.com/gin-gonic/gin/errors.go (100.0%)</option>
<option value="file21">github.com/gin-gonic/gin/fs.go (100.0%)</option>
<option value="file22">github.com/gin-gonic/gin/gin.go (99.6%)</option>
<option value="file23">github.com/gin-gonic/gin/ginS/gins.go (0.0%)</option>
<option value="file24">github.com/gin-gonic/gin/internal/bytesconv/bytesconv.go (100.0%)</option>
<option value="file25">github.com/gin-gonic/gin/internal/fs/fs.go (100.0%)</option>
<option value="file26">github.com/gin-gonic/gin/logger.go (100.0%)</option>
<option value="file27">github.com/gin-gonic/gin/mode.go (100.0%)</option>
<option value="file28">github.com/gin-gonic/gin/path.go (100.0%)</option>
<option value="file29">github.com/gin-gonic/gin/recovery.go (98.6%)</option>
<option value="file30">github.com/gin-gonic/gin/render/data.go (100.0%)</option>
<option value="file31">github.com/gin-gonic/gin/render/html.go (100.0%)</option>
<option value="file32">github.com/gin-gonic/gin/render/json.go (100.0%)</option>
<option value="file33">github.com/gin-gonic/gin/render/msgpack.go (100.0%)</option>
<option value="file34">github.com/gin-gonic/gin/render/protobuf.go (100.0%)</option>
<option value="file35">github.com/gin-gonic/gin/render/reader.go (100.0%)</option>
<option value="file36">github.com/gin-gonic/gin/render/redirect.go (100.0%)</option>
<option value="file37">github.com/gin-gonic/gin/render/render.go (100.0%)</option>
<option value="file38">github.com/gin-gonic/gin/render/text.go (100.0%)</option>
<option value="file39">github.com/gin-gonic/gin/render/toml.go (100.0%)</option>
<option value="file40">github.com/gin-gonic/gin/render/xml.go (100.0%)</option>
<option value="file41">github.com/gin-gonic/gin/render/yaml.go (100.0%)</option>
<option value="file42">github.com/gin-gonic/gin/response_writer.go (100.0%)</option>
<option value="file43">github.com/gin-gonic/gin/routergroup.go (100.0%)</option>
<option value="file44">github.com/gin-gonic/gin/test_helpers.go (100.0%)</option>
<option value="file45">github.com/gin-gonic/gin/tree.go (100.0%)</option>
<option value="file46">github.com/gin-gonic/gin/utils.go (98.4%)</option>
</select>
</div>
<div id="legend">
<span>not tracked</span>
<span class="cov0">not covered</span>
<span class="cov8">covered</span>
</div>
</div>
<div id="content">
<pre class="file" id="file0" style="display: none">// 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) <span class="cov8" title="1">{
if authValue == "" </span><span class="cov8" title="1">{
return "", false
}</span>
<span class="cov8" title="1">for _, pair := range a </span><span class="cov8" title="1">{
if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 </span><span class="cov8" title="1">{
return pair.user, true
}</span>
}
<span class="cov8" title="1">return "", false</span>
}
// 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 <span class="cov8" title="1">{
if realm == "" </span><span class="cov8" title="1">{
realm = "Authorization Required"
}</span>
<span class="cov8" title="1">realm = "Basic realm=" + strconv.Quote(realm)
pairs := processAccounts(accounts)
return func(c *Context) </span><span class="cov8" title="1">{
// Search user in the slice of allowed credentials
user, found := pairs.searchCredential(c.requestHeader("Authorization"))
if !found </span><span class="cov8" title="1">{
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return
}</span>
// 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).
<span class="cov8" title="1">c.Set(AuthUserKey, user)</span>
}
}
// 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 <span class="cov8" title="1">{
return BasicAuthForRealm(accounts, "")
}</span>
func processAccounts(accounts Accounts) authPairs <span class="cov8" title="1">{
length := len(accounts)
assert1(length &gt; 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, length)
for user, password := range accounts </span><span class="cov8" title="1">{
assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, authPair{
value: value,
user: user,
})
}</span>
<span class="cov8" title="1">return pairs</span>
}
func authorizationHeader(user, password string) string <span class="cov8" title="1">{
base := user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
}</span>
// 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 <span class="cov8" title="1">{
if realm == "" </span><span class="cov8" title="1">{
realm = "Proxy Authorization Required"
}</span>
<span class="cov8" title="1">realm = "Basic realm=" + strconv.Quote(realm)
pairs := processAccounts(accounts)
return func(c *Context) </span><span class="cov8" title="1">{
proxyUser, found := pairs.searchCredential(c.requestHeader("Proxy-Authorization"))
if !found </span><span class="cov8" title="1">{
// Credentials doesn't match, we return 407 and abort handlers chain.
c.Header("Proxy-Authenticate", realm)
c.AbortWithStatus(http.StatusProxyAuthRequired)
return
}</span>
// 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).
<span class="cov8" title="1">c.Set(AuthProxyUserKey, proxyUser)</span>
}
}
</pre>
<pre class="file" id="file1" style="display: none">// 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 = &amp;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 <span class="cov8" title="1">{
if method == http.MethodGet </span><span class="cov8" title="1">{
return Form
}</span>
<span class="cov8" title="1">switch contentType </span>{
case MIMEJSON:<span class="cov8" title="1">
return JSON</span>
case MIMEXML, MIMEXML2:<span class="cov8" title="1">
return XML</span>
case MIMEPROTOBUF:<span class="cov8" title="1">
return ProtoBuf</span>
case MIMEMSGPACK, MIMEMSGPACK2:<span class="cov8" title="1">
return MsgPack</span>
case MIMEYAML, MIMEYAML2:<span class="cov8" title="1">
return YAML</span>
case MIMETOML:<span class="cov8" title="1">
return TOML</span>
case MIMEMultipartPOSTForm:<span class="cov8" title="1">
return FormMultipart</span>
default:<span class="cov8" title="1"> // case MIMEPOSTForm:
return Form</span>
}
}
func validate(obj any) error <span class="cov8" title="1">{
if Validator == nil </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">return Validator.ValidateStruct(obj)</span>
}
</pre>
<pre class="file" id="file2" style="display: none">// 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 <span class="cov8" title="1">{
if len(err) == 0 </span><span class="cov8" title="1">{
return ""
}</span>
<span class="cov8" title="1">var b strings.Builder
for i := 0; i &lt; len(err); i++ </span><span class="cov8" title="1">{
if err[i] != nil </span><span class="cov8" title="1">{
if b.Len() &gt; 0 </span><span class="cov8" title="1">{
b.WriteString("\n")
}</span>
<span class="cov8" title="1">b.WriteString("[" + strconv.Itoa(i) + "]: " + err[i].Error())</span>
}
}
<span class="cov8" title="1">return b.String()</span>
}
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 <span class="cov8" title="1">{
if obj == nil </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">value := reflect.ValueOf(obj)
switch value.Kind() </span>{
case reflect.Ptr:<span class="cov8" title="1">
if value.Elem().Kind() != reflect.Struct </span><span class="cov8" title="1">{
return v.ValidateStruct(value.Elem().Interface())
}</span>
<span class="cov8" title="1">return v.validateStruct(obj)</span>
case reflect.Struct:<span class="cov8" title="1">
return v.validateStruct(obj)</span>
case reflect.Slice, reflect.Array:<span class="cov8" title="1">
count := value.Len()
validateRet := make(SliceValidationError, 0)
for i := 0; i &lt; count; i++ </span><span class="cov8" title="1">{
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil </span><span class="cov8" title="1">{
validateRet = append(validateRet, err)
}</span>
}
<span class="cov8" title="1">if len(validateRet) == 0 </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">return validateRet</span>
default:<span class="cov8" title="1">
return nil</span>
}
}
// validateStruct receives struct type
func (v *defaultValidator) validateStruct(obj any) error <span class="cov8" title="1">{
v.lazyinit()
return v.validate.Struct(obj)
}</span>
// 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 <span class="cov8" title="1">{
v.lazyinit()
return v.validate
}</span>
func (v *defaultValidator) lazyinit() <span class="cov8" title="1">{
v.once.Do(func() </span><span class="cov8" title="1">{
v.validate = validator.New()
v.validate.SetTagName("binding")
}</span>)
}
</pre>
<pre class="file" id="file3" style="display: none">// 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 &lt;&lt; 20
type (
formBinding struct{}
formPostBinding struct{}
formMultipartBinding struct{}
)
func (formBinding) Name() string <span class="cov8" title="1">{
return "form"
}</span>
func (formBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
if err := req.ParseForm(); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if err := req.ParseMultipartForm(defaultMemory); err != nil &amp;&amp; !errors.Is(err, http.ErrNotMultipart) </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if err := mapForm(obj, req.Form); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
func (formPostBinding) Name() string <span class="cov8" title="1">{
return "form-urlencoded"
}</span>
func (formPostBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
if err := req.ParseForm(); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if err := mapForm(obj, req.PostForm); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
func (formMultipartBinding) Name() string <span class="cov8" title="1">{
return "multipart/form-data"
}</span>
func (formMultipartBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
if err := req.ParseMultipartForm(defaultMemory); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file4" style="display: none">// 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 <span class="cov8" title="1">{
return mapFormByTag(ptr, m, "uri")
}</span>
func mapForm(ptr any, form map[string][]string) error <span class="cov8" title="1">{
return mapFormByTag(ptr, form, "form")
}</span>
func MapFormWithTag(ptr any, form map[string][]string, tag string) error <span class="cov8" title="1">{
return mapFormByTag(ptr, form, tag)
}</span>
var emptyField = reflect.StructField{}
func mapFormByTag(ptr any, form map[string][]string, tag string) error <span class="cov8" title="1">{
// Check if ptr is a map
ptrVal := reflect.ValueOf(ptr)
var pointed any
if ptrVal.Kind() == reflect.Ptr </span><span class="cov8" title="1">{
ptrVal = ptrVal.Elem()
pointed = ptrVal.Interface()
}</span>
<span class="cov8" title="1">if ptrVal.Kind() == reflect.Map &amp;&amp;
ptrVal.Type().Key().Kind() == reflect.String </span><span class="cov8" title="1">{
if pointed != nil </span><span class="cov8" title="1">{
ptr = pointed
}</span>
<span class="cov8" title="1">return setFormMap(ptr, form)</span>
}
<span class="cov8" title="1">return mappingByPtr(ptr, formSource(form), tag)</span>
}
// 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) <span class="cov8" title="1">{
return setByForm(value, field, form, tagValue, opt)
}</span>
func mappingByPtr(ptr any, setter setter, tag string) error <span class="cov8" title="1">{
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
return err
}</span>
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) <span class="cov8" title="1">{
if field.Tag.Get(tag) == "-" </span><span class="cov8" title="1">{ // just ignoring this field
return false, nil
}</span>
<span class="cov8" title="1">vKind := value.Kind()
if vKind == reflect.Ptr </span><span class="cov8" title="1">{
var isNew bool
vPtr := value
if value.IsNil() </span><span class="cov8" title="1">{
isNew = true
vPtr = reflect.New(value.Type().Elem())
}</span>
<span class="cov8" title="1">isSet, err := mapping(vPtr.Elem(), field, setter, tag)
if err != nil </span><span class="cov8" title="1">{
return false, err
}</span>
<span class="cov8" title="1">if isNew &amp;&amp; isSet </span><span class="cov8" title="1">{
value.Set(vPtr)
}</span>
<span class="cov8" title="1">return isSet, nil</span>
}
<span class="cov8" title="1">if vKind != reflect.Struct || !field.Anonymous </span><span class="cov8" title="1">{
ok, err := tryToSetValue(value, field, setter, tag)
if err != nil </span><span class="cov8" title="1">{
return false, err
}</span>
<span class="cov8" title="1">if ok </span><span class="cov8" title="1">{
return true, nil
}</span>
}
<span class="cov8" title="1">if vKind == reflect.Struct </span><span class="cov8" title="1">{
tValue := value.Type()
var isSet bool
for i := 0; i &lt; value.NumField(); i++ </span><span class="cov8" title="1">{
sf := tValue.Field(i)
if sf.PkgPath != "" &amp;&amp; !sf.Anonymous </span><span class="cov8" title="1">{ // unexported
continue</span>
}
<span class="cov8" title="1">ok, err := mapping(value.Field(i), sf, setter, tag)
if err != nil </span><span class="cov8" title="1">{
return false, err
}</span>
<span class="cov8" title="1">isSet = isSet || ok</span>
}
<span class="cov8" title="1">return isSet, nil</span>
}
<span class="cov8" title="1">return false, nil</span>
}
type setOptions struct {
isDefaultExists bool
defaultValue string
}
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) <span class="cov8" title="1">{
var tagValue string
var setOpt setOptions
tagValue = field.Tag.Get(tag)
tagValue, opts := head(tagValue, ",")
if tagValue == "" </span><span class="cov8" title="1">{ // default value is FieldName
tagValue = field.Name
}</span>
<span class="cov8" title="1">if tagValue == "" </span><span class="cov8" title="1">{ // when field is "emptyField" variable
return false, nil
}</span>
<span class="cov8" title="1">var opt string
for len(opts) &gt; 0 </span><span class="cov8" title="1">{
opt, opts = head(opts, ",")
if k, v := head(opt, "="); k == "default" </span><span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
cfTag := field.Tag.Get("collection_format")
if cfTag == "" || cfTag == "multi" || cfTag == "csv" </span><span class="cov8" title="1">{
setOpt.defaultValue = strings.ReplaceAll(v, ";", ",")
}</span>
}
}
}
<span class="cov8" title="1">return setter.TrySet(value, field, tagValue, setOpt)</span>
}
// 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) <span class="cov8" title="1">{
switch v := value.Addr().Interface().(type) </span>{
case BindUnmarshaler:<span class="cov8" title="1">
return true, v.UnmarshalParam(val)</span>
}
<span class="cov8" title="1">return false, nil</span>
}
func trySplit(vs []string, field reflect.StructField) (newVs []string, err error) <span class="cov8" title="1">{
cfTag := field.Tag.Get("collection_format")
if cfTag == "" || cfTag == "multi" </span><span class="cov8" title="1">{
return vs, nil
}</span>
<span class="cov8" title="1">var sep string
switch cfTag </span>{
case "csv":<span class="cov8" title="1">
sep = ","</span>
case "ssv":<span class="cov8" title="1">
sep = " "</span>
case "tsv":<span class="cov8" title="1">
sep = "\t"</span>
case "pipes":<span class="cov8" title="1">
sep = "|"</span>
default:<span class="cov8" title="1">
return vs, fmt.Errorf("%s is not supported in the collection_format. (csv, ssv, pipes)", cfTag)</span>
}
<span class="cov8" title="1">totalLength := 0
for _, v := range vs </span><span class="cov8" title="1">{
totalLength += strings.Count(v, sep) + 1
}</span>
<span class="cov8" title="1">newVs = make([]string, 0, totalLength)
for _, v := range vs </span><span class="cov8" title="1">{
newVs = append(newVs, strings.Split(v, sep)...)
}</span>
<span class="cov8" title="1">return newVs, nil</span>
}
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) <span class="cov8" title="1">{
vs, ok := form[tagValue]
if !ok &amp;&amp; !opt.isDefaultExists </span><span class="cov8" title="1">{
return false, nil
}</span>
<span class="cov8" title="1">switch value.Kind() </span>{
case reflect.Slice:<span class="cov8" title="1">
if !ok </span><span class="cov8" title="1">{
vs = []string{opt.defaultValue}
// pre-process the default value for multi if present
cfTag := field.Tag.Get("collection_format")
if cfTag == "" || cfTag == "multi" </span><span class="cov8" title="1">{
vs = strings.Split(opt.defaultValue, ",")
}</span>
}
<span class="cov8" title="1">if ok, err = trySetCustom(vs[0], value); ok </span><span class="cov8" title="1">{
return ok, err
}</span>
<span class="cov8" title="1">if vs, err = trySplit(vs, field); err != nil </span><span class="cov8" title="1">{
return false, err
}</span>
<span class="cov8" title="1">return true, setSlice(vs, value, field)</span>
case reflect.Array:<span class="cov8" title="1">
if !ok </span><span class="cov8" title="1">{
vs = []string{opt.defaultValue}
// pre-process the default value for multi if present
cfTag := field.Tag.Get("collection_format")
if cfTag == "" || cfTag == "multi" </span><span class="cov8" title="1">{
vs = strings.Split(opt.defaultValue, ",")
}</span>
}
<span class="cov8" title="1">if ok, err = trySetCustom(vs[0], value); ok </span><span class="cov8" title="1">{
return ok, err
}</span>
<span class="cov8" title="1">if vs, err = trySplit(vs, field); err != nil </span><span class="cov8" title="1">{
return false, err
}</span>
<span class="cov8" title="1">if len(vs) != value.Len() </span><span class="cov8" title="1">{
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
}</span>
<span class="cov8" title="1">return true, setArray(vs, value, field)</span>
default:<span class="cov8" title="1">
var val string
if !ok </span><span class="cov8" title="1">{
val = opt.defaultValue
}</span>
<span class="cov8" title="1">if len(vs) &gt; 0 </span><span class="cov8" title="1">{
val = vs[0]
if val == "" </span><span class="cov8" title="1">{
val = opt.defaultValue
}</span>
}
<span class="cov8" title="1">if ok, err := trySetCustom(val, value); ok </span><span class="cov8" title="1">{
return ok, err
}</span>
<span class="cov8" title="1">return true, setWithProperType(val, value, field)</span>
}
}
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error <span class="cov8" title="1">{
switch value.Kind() </span>{
case reflect.Int:<span class="cov8" title="1">
return setIntField(val, 0, value)</span>
case reflect.Int8:<span class="cov8" title="1">
return setIntField(val, 8, value)</span>
case reflect.Int16:<span class="cov8" title="1">
return setIntField(val, 16, value)</span>
case reflect.Int32:<span class="cov8" title="1">
return setIntField(val, 32, value)</span>
case reflect.Int64:<span class="cov8" title="1">
switch value.Interface().(type) </span>{
case time.Duration:<span class="cov8" title="1">
return setTimeDuration(val, value)</span>
}
<span class="cov8" title="1">return setIntField(val, 64, value)</span>
case reflect.Uint:<span class="cov8" title="1">
return setUintField(val, 0, value)</span>
case reflect.Uint8:<span class="cov8" title="1">
return setUintField(val, 8, value)</span>
case reflect.Uint16:<span class="cov8" title="1">
return setUintField(val, 16, value)</span>
case reflect.Uint32:<span class="cov8" title="1">
return setUintField(val, 32, value)</span>
case reflect.Uint64:<span class="cov8" title="1">
return setUintField(val, 64, value)</span>
case reflect.Bool:<span class="cov8" title="1">
return setBoolField(val, value)</span>
case reflect.Float32:<span class="cov8" title="1">
return setFloatField(val, 32, value)</span>
case reflect.Float64:<span class="cov8" title="1">
return setFloatField(val, 64, value)</span>
case reflect.String:<span class="cov8" title="1">
value.SetString(val)</span>
case reflect.Struct:<span class="cov8" title="1">
switch value.Interface().(type) </span>{
case time.Time:<span class="cov8" title="1">
return setTimeField(val, field, value)</span>
case multipart.FileHeader:<span class="cov8" title="1">
return nil</span>
}
<span class="cov8" title="1">return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())</span>
case reflect.Map:<span class="cov8" title="1">
return json.API.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())</span>
case reflect.Ptr:<span class="cov8" title="1">
if !value.Elem().IsValid() </span><span class="cov8" title="1">{
value.Set(reflect.New(value.Type().Elem()))
}</span>
<span class="cov8" title="1">return setWithProperType(val, value.Elem(), field)</span>
default:<span class="cov8" title="1">
return errUnknownType</span>
}
<span class="cov8" title="1">return nil</span>
}
func setIntField(val string, bitSize int, field reflect.Value) error <span class="cov8" title="1">{
if val == "" </span><span class="cov8" title="1">{
val = "0"
}</span>
<span class="cov8" title="1">intVal, err := strconv.ParseInt(val, 10, bitSize)
if err == nil </span><span class="cov8" title="1">{
field.SetInt(intVal)
}</span>
<span class="cov8" title="1">return err</span>
}
func setUintField(val string, bitSize int, field reflect.Value) error <span class="cov8" title="1">{
if val == "" </span><span class="cov8" title="1">{
val = "0"
}</span>
<span class="cov8" title="1">uintVal, err := strconv.ParseUint(val, 10, bitSize)
if err == nil </span><span class="cov8" title="1">{
field.SetUint(uintVal)
}</span>
<span class="cov8" title="1">return err</span>
}
func setBoolField(val string, field reflect.Value) error <span class="cov8" title="1">{
if val == "" </span><span class="cov8" title="1">{
val = "false"
}</span>
<span class="cov8" title="1">boolVal, err := strconv.ParseBool(val)
if err == nil </span><span class="cov8" title="1">{
field.SetBool(boolVal)
}</span>
<span class="cov8" title="1">return err</span>
}
func setFloatField(val string, bitSize int, field reflect.Value) error <span class="cov8" title="1">{
if val == "" </span><span class="cov8" title="1">{
val = "0.0"
}</span>
<span class="cov8" title="1">floatVal, err := strconv.ParseFloat(val, bitSize)
if err == nil </span><span class="cov8" title="1">{
field.SetFloat(floatVal)
}</span>
<span class="cov8" title="1">return err</span>
}
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error <span class="cov8" title="1">{
timeFormat := structField.Tag.Get("time_format")
if timeFormat == "" </span><span class="cov8" title="1">{
timeFormat = time.RFC3339
}</span>
<span class="cov8" title="1">switch tf := strings.ToLower(timeFormat); tf </span>{
case "unix", "unixmilli", "unixmicro", "unixnano":<span class="cov8" title="1">
tv, err := strconv.ParseInt(val, 10, 64)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">var t time.Time
switch tf </span>{
case "unix":<span class="cov8" title="1">
t = time.Unix(tv, 0)</span>
case "unixmilli":<span class="cov8" title="1">
t = time.UnixMilli(tv)</span>
case "unixmicro":<span class="cov8" title="1">
t = time.UnixMicro(tv)</span>
default:<span class="cov8" title="1">
t = time.Unix(0, tv)</span>
}
<span class="cov8" title="1">value.Set(reflect.ValueOf(t))
return nil</span>
}
<span class="cov8" title="1">if val == "" </span><span class="cov8" title="1">{
value.Set(reflect.ValueOf(time.Time{}))
return nil
}</span>
<span class="cov8" title="1">l := time.Local
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC </span><span class="cov8" title="1">{
l = time.UTC
}</span>
<span class="cov8" title="1">if locTag := structField.Tag.Get("time_location"); locTag != "" </span><span class="cov8" title="1">{
loc, err := time.LoadLocation(locTag)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">l = loc</span>
}
<span class="cov8" title="1">t, err := time.ParseInLocation(timeFormat, val, l)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">value.Set(reflect.ValueOf(t))
return nil</span>
}
func setArray(vals []string, value reflect.Value, field reflect.StructField) error <span class="cov8" title="1">{
for i, s := range vals </span><span class="cov8" title="1">{
err := setWithProperType(s, value.Index(i), field)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
}
<span class="cov8" title="1">return nil</span>
}
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error <span class="cov8" title="1">{
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
err := setArray(vals, slice, field)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">value.Set(slice)
return nil</span>
}
func setTimeDuration(val string, value reflect.Value) error <span class="cov8" title="1">{
d, err := time.ParseDuration(val)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">value.Set(reflect.ValueOf(d))
return nil</span>
}
func head(str, sep string) (head string, tail string) <span class="cov8" title="1">{
head, tail, _ = strings.Cut(str, sep)
return head, tail
}</span>
func setFormMap(ptr any, form map[string][]string) error <span class="cov8" title="1">{
el := reflect.TypeOf(ptr).Elem()
if el.Kind() == reflect.Slice </span><span class="cov8" title="1">{
ptrMap, ok := ptr.(map[string][]string)
if !ok </span><span class="cov8" title="1">{
return ErrConvertMapStringSlice
}</span>
<span class="cov8" title="1">for k, v := range form </span><span class="cov8" title="1">{
ptrMap[k] = v
}</span>
<span class="cov8" title="1">return nil</span>
}
<span class="cov8" title="1">ptrMap, ok := ptr.(map[string]string)
if !ok </span><span class="cov8" title="1">{
return ErrConvertToMapString
}</span>
<span class="cov8" title="1">for k, v := range form </span><span class="cov8" title="1">{
ptrMap[k] = v[len(v)-1] // pick last
}</span>
<span class="cov8" title="1">return nil</span>
}
</pre>
<pre class="file" id="file5" style="display: none">// 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 <span class="cov8" title="1">{
return "header"
}</span>
func (headerBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
if err := mapHeader(obj, req.Header); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
func mapHeader(ptr any, h map[string][]string) error <span class="cov8" title="1">{
return mappingByPtr(ptr, headerSource(h), "header")
}</span>
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) <span class="cov8" title="1">{
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
}</span>
</pre>
<pre class="file" id="file6" style="display: none">// 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 <span class="cov8" title="1">{
return "json"
}</span>
func (jsonBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
if req == nil || req.Body == nil </span><span class="cov8" title="1">{
return errors.New("invalid request")
}</span>
<span class="cov8" title="1">return decodeJSON(req.Body, obj)</span>
}
func (jsonBinding) BindBody(body []byte, obj any) error <span class="cov8" title="1">{
return decodeJSON(bytes.NewReader(body), obj)
}</span>
func decodeJSON(r io.Reader, obj any) error <span class="cov8" title="1">{
decoder := json.API.NewDecoder(r)
if EnableDecoderUseNumber </span><span class="cov8" title="1">{
decoder.UseNumber()
}</span>
<span class="cov8" title="1">if EnableDecoderDisallowUnknownFields </span><span class="cov8" title="1">{
decoder.DisallowUnknownFields()
}</span>
<span class="cov8" title="1">if err := decoder.Decode(obj); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file7" style="display: none">// 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 <span class="cov8" title="1">{
return "msgpack"
}</span>
func (msgpackBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
return decodeMsgPack(req.Body, obj)
}</span>
func (msgpackBinding) BindBody(body []byte, obj any) error <span class="cov8" title="1">{
return decodeMsgPack(bytes.NewReader(body), obj)
}</span>
func decodeMsgPack(r io.Reader, obj any) error <span class="cov8" title="1">{
cdc := new(codec.MsgpackHandle)
if err := codec.NewDecoder(r, cdc).Decode(&amp;obj); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file8" style="display: none">// 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) <span class="cov8" title="1">{
if files := r.MultipartForm.File[key]; len(files) != 0 </span><span class="cov8" title="1">{
return setByMultipartFormFile(value, field, files)
}</span>
<span class="cov8" title="1">return setByForm(value, field, r.MultipartForm.Value, key, opt)</span>
}
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) <span class="cov8" title="1">{
switch value.Kind() </span>{
case reflect.Ptr:<span class="cov8" title="1">
switch value.Interface().(type) </span>{
case *multipart.FileHeader:<span class="cov8" title="1">
value.Set(reflect.ValueOf(files[0]))
return true, nil</span>
}
case reflect.Struct:<span class="cov8" title="1">
switch value.Interface().(type) </span>{
case multipart.FileHeader:<span class="cov8" title="1">
value.Set(reflect.ValueOf(*files[0]))
return true, nil</span>
}
case reflect.Slice:<span class="cov8" title="1">
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
if err != nil || !isSet </span><span class="cov8" title="1">{
return isSet, err
}</span>
<span class="cov8" title="1">value.Set(slice)
return true, nil</span>
case reflect.Array:<span class="cov8" title="1">
return setArrayOfMultipartFormFiles(value, field, files)</span>
}
<span class="cov8" title="1">return false, ErrMultiFileHeader</span>
}
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) <span class="cov8" title="1">{
if value.Len() != len(files) </span><span class="cov8" title="1">{
return false, ErrMultiFileHeaderLenInvalid
}</span>
<span class="cov8" title="1">for i := range files </span><span class="cov8" title="1">{
set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
if err != nil || !set </span><span class="cov8" title="1">{
return set, err
}</span>
}
<span class="cov8" title="1">return true, nil</span>
}
</pre>
<pre class="file" id="file9" style="display: none">package binding
import (
"fmt"
"io"
"net/http"
"reflect"
"github.com/gin-gonic/gin/internal/bytesconv"
)
type plainBinding struct{}
func (plainBinding) Name() string <span class="cov8" title="1">{
return "plain"
}</span>
func (plainBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
all, err := io.ReadAll(req.Body)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return decodePlain(all, obj)</span>
}
func (plainBinding) BindBody(body []byte, obj any) error <span class="cov0" title="0">{
return decodePlain(body, obj)
}</span>
func decodePlain(data []byte, obj any) error <span class="cov8" title="1">{
if obj == nil </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">v := reflect.ValueOf(obj)
for v.Kind() == reflect.Ptr </span><span class="cov8" title="1">{
if v.IsNil() </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">v = v.Elem()</span>
}
<span class="cov8" title="1">if v.Kind() == reflect.String </span><span class="cov8" title="1">{
v.SetString(bytesconv.BytesToString(data))
return nil
}</span>
<span class="cov8" title="1">if _, ok := v.Interface().([]byte); ok </span><span class="cov8" title="1">{
v.SetBytes(data)
return nil
}</span>
<span class="cov8" title="1">return fmt.Errorf("type (%T) unknown type", v)</span>
}
</pre>
<pre class="file" id="file10" style="display: none">// 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 <span class="cov8" title="1">{
return "protobuf"
}</span>
func (b protobufBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
buf, err := io.ReadAll(req.Body)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return b.BindBody(buf, obj)</span>
}
func (protobufBinding) BindBody(body []byte, obj any) error <span class="cov8" title="1">{
msg, ok := obj.(proto.Message)
if !ok </span><span class="cov8" title="1">{
return errors.New("obj is not ProtoMessage")
}</span>
<span class="cov8" title="1">if err := proto.Unmarshal(body, msg); err != nil </span><span class="cov8" title="1">{
return err
}</span>
// 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
<span class="cov8" title="1">return nil</span>
// return validate(obj)
}
</pre>
<pre class="file" id="file11" style="display: none">// 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 <span class="cov8" title="1">{
return "query"
}</span>
func (queryBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
values := req.URL.Query()
if err := mapForm(obj, values); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file12" style="display: none">// 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 <span class="cov8" title="1">{
return "toml"
}</span>
func (tomlBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
return decodeToml(req.Body, obj)
}</span>
func (tomlBinding) BindBody(body []byte, obj any) error <span class="cov8" title="1">{
return decodeToml(bytes.NewReader(body), obj)
}</span>
func decodeToml(r io.Reader, obj any) error <span class="cov8" title="1">{
decoder := toml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file13" style="display: none">// 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 <span class="cov8" title="1">{
return "uri"
}</span>
func (uriBinding) BindUri(m map[string][]string, obj any) error <span class="cov8" title="1">{
if err := mapURI(obj, m); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file14" style="display: none">// 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 <span class="cov8" title="1">{
return "xml"
}</span>
func (xmlBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
return decodeXML(req.Body, obj)
}</span>
func (xmlBinding) BindBody(body []byte, obj any) error <span class="cov8" title="1">{
return decodeXML(bytes.NewReader(body), obj)
}</span>
func decodeXML(r io.Reader, obj any) error <span class="cov8" title="1">{
decoder := xml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file15" style="display: none">// 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 <span class="cov8" title="1">{
return "yaml"
}</span>
func (yamlBinding) Bind(req *http.Request, obj any) error <span class="cov8" title="1">{
return decodeYAML(req.Body, obj)
}</span>
func (yamlBinding) BindBody(body []byte, obj any) error <span class="cov8" title="1">{
return decodeYAML(bytes.NewReader(body), obj)
}</span>
func decodeYAML(r io.Reader, obj any) error <span class="cov8" title="1">{
decoder := yaml.NewDecoder(r)
if err := decoder.Decode(obj); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return validate(obj)</span>
}
</pre>
<pre class="file" id="file16" style="display: none">// 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 &amp;&amp; !go_json &amp;&amp; !(sonic &amp;&amp; (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() <span class="cov0" title="0">{
API = jsonApi{}
}</span>
type jsonApi struct{}
func (j jsonApi) Marshal(v any) ([]byte, error) <span class="cov0" title="0">{
return json.Marshal(v)
}</span>
func (j jsonApi) Unmarshal(data []byte, v any) error <span class="cov0" title="0">{
return json.Unmarshal(data, v)
}</span>
func (j jsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) <span class="cov0" title="0">{
return json.MarshalIndent(v, prefix, indent)
}</span>
func (j jsonApi) NewEncoder(writer io.Writer) Encoder <span class="cov0" title="0">{
return json.NewEncoder(writer)
}</span>
func (j jsonApi) NewDecoder(reader io.Reader) Decoder <span class="cov0" title="0">{
return json.NewDecoder(reader)
}</span>
</pre>
<pre class="file" id="file17" style="display: none">// 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 &gt;&gt; 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() <span class="cov8" title="1">{
c.Writer = &amp;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]
}</span>
// 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 <span class="cov8" title="1">{
cp := Context{
writermem: c.writermem,
Request: c.Request,
engine: c.engine,
}
cp.writermem.ResponseWriter = nil
cp.Writer = &amp;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 </span><span class="cov8" title="1">{
cp.Keys[k] = v
}</span>
<span class="cov8" title="1">c.mu.RUnlock()
cParams := c.Params
cp.Params = make([]Param, len(cParams))
copy(cp.Params, cParams)
return &amp;cp</span>
}
// 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 <span class="cov8" title="1">{
return nameOfFunction(c.handlers.Last())
}</span>
// HandlerNames returns a list of all registered handlers for this context in descending order,
// following the semantics of HandlerName()
func (c *Context) HandlerNames() []string <span class="cov8" title="1">{
hn := make([]string, 0, len(c.handlers))
for _, val := range c.handlers </span><span class="cov8" title="1">{
if val == nil </span><span class="cov8" title="1">{
continue</span>
}
<span class="cov8" title="1">hn = append(hn, nameOfFunction(val))</span>
}
<span class="cov8" title="1">return hn</span>
}
// Handler returns the main handler.
func (c *Context) Handler() HandlerFunc <span class="cov8" title="1">{
return c.handlers.Last()
}</span>
// 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 <span class="cov8" title="1">{
return c.fullPath
}</span>
/************************************/
/*********** 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() <span class="cov8" title="1">{
c.index++
for c.index &lt; int8(len(c.handlers)) </span><span class="cov8" title="1">{
if c.handlers[c.index] != nil </span><span class="cov8" title="1">{
c.handlers[c.index](c)
}</span>
<span class="cov8" title="1">c.index++</span>
}
}
// IsAborted returns true if the current context was aborted.
func (c *Context) IsAborted() bool <span class="cov8" title="1">{
return c.index &gt;= abortIndex
}</span>
// 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() <span class="cov8" title="1">{
c.index = abortIndex
}</span>
// 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) <span class="cov8" title="1">{
c.Status(code)
c.Writer.WriteHeaderNow()
c.Abort()
}</span>
// 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) <span class="cov8" title="1">{
c.Abort()
c.PureJSON(code, jsonObj)
}</span>
// 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) <span class="cov8" title="1">{
c.Abort()
c.JSON(code, jsonObj)
}</span>
// 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 <span class="cov8" title="1">{
c.AbortWithStatus(code)
return c.Error(err)
}</span>
/************************************/
/********* 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 <span class="cov8" title="1">{
if err == nil </span><span class="cov8" title="1">{
panic("err is nil")</span>
}
<span class="cov8" title="1">var parsedError *Error
ok := errors.As(err, &amp;parsedError)
if !ok </span><span class="cov8" title="1">{
parsedError = &amp;Error{
Err: err,
Type: ErrorTypePrivate,
}
}</span>
<span class="cov8" title="1">c.Errors = append(c.Errors, parsedError)
return parsedError</span>
}
/************************************/
/******** 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) <span class="cov8" title="1">{
c.mu.Lock()
defer c.mu.Unlock()
if c.Keys == nil </span><span class="cov8" title="1">{
c.Keys = make(map[any]any)
}</span>
<span class="cov8" title="1">c.Keys[key] = value</span>
}
// 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) <span class="cov8" title="1">{
c.mu.RLock()
defer c.mu.RUnlock()
value, exists = c.Keys[key]
return
}</span>
// MustGet returns the value for the given key if it exists, otherwise it panics.
func (c *Context) MustGet(key any) any <span class="cov8" title="1">{
if value, exists := c.Get(key); exists </span><span class="cov8" title="1">{
return value
}</span>
<span class="cov8" title="1">panic(fmt.Sprintf("key %v does not exist", key))</span>
}
func getTyped[T any](c *Context, key any) (res T) <span class="cov8" title="1">{
if val, ok := c.Get(key); ok &amp;&amp; val != nil </span><span class="cov8" title="1">{
res, _ = val.(T)
}</span>
<span class="cov8" title="1">return</span>
}
// GetString returns the value associated with the key as a string.
func (c *Context) GetString(key any) (s string) <span class="cov8" title="1">{
return getTyped[string](c, key)
}</span>
// GetBool returns the value associated with the key as a boolean.
func (c *Context) GetBool(key any) (b bool) <span class="cov8" title="1">{
return getTyped[bool](c, key)
}</span>
// GetInt returns the value associated with the key as an integer.
func (c *Context) GetInt(key any) (i int) <span class="cov8" title="1">{
return getTyped[int](c, key)
}</span>
// GetInt8 returns the value associated with the key as an integer 8.
func (c *Context) GetInt8(key any) (i8 int8) <span class="cov8" title="1">{
return getTyped[int8](c, key)
}</span>
// GetInt16 returns the value associated with the key as an integer 16.
func (c *Context) GetInt16(key any) (i16 int16) <span class="cov8" title="1">{
return getTyped[int16](c, key)
}</span>
// GetInt32 returns the value associated with the key as an integer 32.
func (c *Context) GetInt32(key any) (i32 int32) <span class="cov8" title="1">{
return getTyped[int32](c, key)
}</span>
// GetInt64 returns the value associated with the key as an integer 64.
func (c *Context) GetInt64(key any) (i64 int64) <span class="cov8" title="1">{
return getTyped[int64](c, key)
}</span>
// GetUint returns the value associated with the key as an unsigned integer.
func (c *Context) GetUint(key any) (ui uint) <span class="cov8" title="1">{
return getTyped[uint](c, key)
}</span>
// GetUint8 returns the value associated with the key as an unsigned integer 8.
func (c *Context) GetUint8(key any) (ui8 uint8) <span class="cov8" title="1">{
return getTyped[uint8](c, key)
}</span>
// GetUint16 returns the value associated with the key as an unsigned integer 16.
func (c *Context) GetUint16(key any) (ui16 uint16) <span class="cov8" title="1">{
return getTyped[uint16](c, key)
}</span>
// GetUint32 returns the value associated with the key as an unsigned integer 32.
func (c *Context) GetUint32(key any) (ui32 uint32) <span class="cov8" title="1">{
return getTyped[uint32](c, key)
}</span>
// GetUint64 returns the value associated with the key as an unsigned integer 64.
func (c *Context) GetUint64(key any) (ui64 uint64) <span class="cov8" title="1">{
return getTyped[uint64](c, key)
}</span>
// GetFloat32 returns the value associated with the key as a float32.
func (c *Context) GetFloat32(key any) (f32 float32) <span class="cov8" title="1">{
return getTyped[float32](c, key)
}</span>
// GetFloat64 returns the value associated with the key as a float64.
func (c *Context) GetFloat64(key any) (f64 float64) <span class="cov8" title="1">{
return getTyped[float64](c, key)
}</span>
// GetTime returns the value associated with the key as time.
func (c *Context) GetTime(key any) (t time.Time) <span class="cov8" title="1">{
return getTyped[time.Time](c, key)
}</span>
// GetDuration returns the value associated with the key as a duration.
func (c *Context) GetDuration(key any) (d time.Duration) <span class="cov8" title="1">{
return getTyped[time.Duration](c, key)
}</span>
// GetIntSlice returns the value associated with the key as a slice of integers.
func (c *Context) GetIntSlice(key any) (is []int) <span class="cov8" title="1">{
return getTyped[[]int](c, key)
}</span>
// GetInt8Slice returns the value associated with the key as a slice of int8 integers.
func (c *Context) GetInt8Slice(key any) (i8s []int8) <span class="cov8" title="1">{
return getTyped[[]int8](c, key)
}</span>
// GetInt16Slice returns the value associated with the key as a slice of int16 integers.
func (c *Context) GetInt16Slice(key any) (i16s []int16) <span class="cov8" title="1">{
return getTyped[[]int16](c, key)
}</span>
// GetInt32Slice returns the value associated with the key as a slice of int32 integers.
func (c *Context) GetInt32Slice(key any) (i32s []int32) <span class="cov8" title="1">{
return getTyped[[]int32](c, key)
}</span>
// GetInt64Slice returns the value associated with the key as a slice of int64 integers.
func (c *Context) GetInt64Slice(key any) (i64s []int64) <span class="cov8" title="1">{
return getTyped[[]int64](c, key)
}</span>
// GetUintSlice returns the value associated with the key as a slice of unsigned integers.
func (c *Context) GetUintSlice(key any) (uis []uint) <span class="cov8" title="1">{
return getTyped[[]uint](c, key)
}</span>
// GetUint8Slice returns the value associated with the key as a slice of uint8 integers.
func (c *Context) GetUint8Slice(key any) (ui8s []uint8) <span class="cov8" title="1">{
return getTyped[[]uint8](c, key)
}</span>
// GetUint16Slice returns the value associated with the key as a slice of uint16 integers.
func (c *Context) GetUint16Slice(key any) (ui16s []uint16) <span class="cov8" title="1">{
return getTyped[[]uint16](c, key)
}</span>
// GetUint32Slice returns the value associated with the key as a slice of uint32 integers.
func (c *Context) GetUint32Slice(key any) (ui32s []uint32) <span class="cov8" title="1">{
return getTyped[[]uint32](c, key)
}</span>
// GetUint64Slice returns the value associated with the key as a slice of uint64 integers.
func (c *Context) GetUint64Slice(key any) (ui64s []uint64) <span class="cov8" title="1">{
return getTyped[[]uint64](c, key)
}</span>
// GetFloat32Slice returns the value associated with the key as a slice of float32 numbers.
func (c *Context) GetFloat32Slice(key any) (f32s []float32) <span class="cov8" title="1">{
return getTyped[[]float32](c, key)
}</span>
// GetFloat64Slice returns the value associated with the key as a slice of float64 numbers.
func (c *Context) GetFloat64Slice(key any) (f64s []float64) <span class="cov8" title="1">{
return getTyped[[]float64](c, key)
}</span>
// GetStringSlice returns the value associated with the key as a slice of strings.
func (c *Context) GetStringSlice(key any) (ss []string) <span class="cov8" title="1">{
return getTyped[[]string](c, key)
}</span>
// GetStringMap returns the value associated with the key as a map of interfaces.
func (c *Context) GetStringMap(key any) (sm map[string]any) <span class="cov8" title="1">{
return getTyped[map[string]any](c, key)
}</span>
// GetStringMapString returns the value associated with the key as a map of strings.
func (c *Context) GetStringMapString(key any) (sms map[string]string) <span class="cov8" title="1">{
return getTyped[map[string]string](c, key)
}</span>
// 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) <span class="cov8" title="1">{
return getTyped[map[string][]string](c, key)
}</span>
/************************************/
/************ 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 <span class="cov8" title="1">{
return c.Params.ByName(key)
}</span>
// 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) <span class="cov8" title="1">{
c.Params = append(c.Params, Param{Key: key, Value: value})
}</span>
// 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&amp;name=Manu&amp;value=
// c.Query("id") == "1234"
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) (value string) <span class="cov8" title="1">{
value, _ = c.GetQuery(key)
return
}</span>
// 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&amp;lastname=
// c.DefaultQuery("name", "unknown") == "Manu"
// c.DefaultQuery("id", "none") == "none"
// c.DefaultQuery("lastname", "none") == ""
func (c *Context) DefaultQuery(key, defaultValue string) string <span class="cov8" title="1">{
if value, ok := c.GetQuery(key); ok </span><span class="cov8" title="1">{
return value
}</span>
<span class="cov8" title="1">return defaultValue</span>
}
// 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&amp;lastname=
// ("Manu", true) == c.GetQuery("name")
// ("", false) == c.GetQuery("id")
// ("", true) == c.GetQuery("lastname")
func (c *Context) GetQuery(key string) (string, bool) <span class="cov8" title="1">{
if values, ok := c.GetQueryArray(key); ok </span><span class="cov8" title="1">{
return values[0], ok
}</span>
<span class="cov8" title="1">return "", false</span>
}
// 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) <span class="cov8" title="1">{
values, _ = c.GetQueryArray(key)
return
}</span>
func (c *Context) initQueryCache() <span class="cov8" title="1">{
if c.queryCache == nil </span><span class="cov8" title="1">{
if c.Request != nil &amp;&amp; c.Request.URL != nil </span><span class="cov8" title="1">{
c.queryCache = c.Request.URL.Query()
}</span> else<span class="cov8" title="1"> {
c.queryCache = url.Values{}
}</span>
}
}
// 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) <span class="cov8" title="1">{
c.initQueryCache()
values, ok = c.queryCache[key]
return
}</span>
// QueryMap returns a map for a given query key.
func (c *Context) QueryMap(key string) (dicts map[string]string) <span class="cov8" title="1">{
dicts, _ = c.GetQueryMap(key)
return
}</span>
// 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) <span class="cov8" title="1">{
c.initQueryCache()
return c.get(c.queryCache, key)
}</span>
// 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) <span class="cov8" title="1">{
value, _ = c.GetPostForm(key)
return
}</span>
// 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 <span class="cov8" title="1">{
if value, ok := c.GetPostForm(key); ok </span><span class="cov8" title="1">{
return value
}</span>
<span class="cov8" title="1">return defaultValue</span>
}
// 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 --&gt; ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
// email= --&gt; ("", true) := GetPostForm("email") // set email to ""
// --&gt; ("", false) := GetPostForm("email") // do nothing with email
func (c *Context) GetPostForm(key string) (string, bool) <span class="cov8" title="1">{
if values, ok := c.GetPostFormArray(key); ok </span><span class="cov8" title="1">{
return values[0], ok
}</span>
<span class="cov8" title="1">return "", false</span>
}
// 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) <span class="cov8" title="1">{
values, _ = c.GetPostFormArray(key)
return
}</span>
func (c *Context) initFormCache() <span class="cov8" title="1">{
if c.formCache == nil </span><span class="cov8" title="1">{
c.formCache = make(url.Values)
req := c.Request
if err := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil </span><span class="cov8" title="1">{
if !errors.Is(err, http.ErrNotMultipart) </span><span class="cov8" title="1">{
debugPrint("error on parse multipart form array: %v", err)
}</span>
}
<span class="cov8" title="1">c.formCache = req.PostForm</span>
}
}
// 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) <span class="cov8" title="1">{
c.initFormCache()
values, ok = c.formCache[key]
return
}</span>
// PostFormMap returns a map for a given form key.
func (c *Context) PostFormMap(key string) (dicts map[string]string) <span class="cov8" title="1">{
dicts, _ = c.GetPostFormMap(key)
return
}</span>
// 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) <span class="cov8" title="1">{
c.initFormCache()
return c.get(c.formCache, key)
}</span>
// 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) <span class="cov8" title="1">{
dicts := make(map[string]string)
exist := false
for k, v := range m </span><span class="cov8" title="1">{
if i := strings.IndexByte(k, '['); i &gt;= 1 &amp;&amp; k[0:i] == key </span><span class="cov8" title="1">{
if j := strings.IndexByte(k[i+1:], ']'); j &gt;= 1 </span><span class="cov8" title="1">{
exist = true
dicts[k[i+1:][:j]] = v[0]
}</span>
}
}
<span class="cov8" title="1">return dicts, exist</span>
}
// FormFile returns the first file for the provided form key.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) <span class="cov8" title="1">{
if c.Request.MultipartForm == nil </span><span class="cov8" title="1">{
if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil </span><span class="cov8" title="1">{
return nil, err
}</span>
}
<span class="cov8" title="1">f, fh, err := c.Request.FormFile(name)
if err != nil </span><span class="cov0" title="0">{
return nil, err
}</span>
<span class="cov8" title="1">f.Close()
return fh, err</span>
}
// MultipartForm is the parsed multipart form, including file uploads.
func (c *Context) MultipartForm() (*multipart.Form, error) <span class="cov8" title="1">{
err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)
return c.Request.MultipartForm, err
}</span>
// SaveUploadedFile uploads the form file to specific dst.
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string, perm ...fs.FileMode) error <span class="cov8" title="1">{
src, err := file.Open()
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">defer src.Close()
var mode os.FileMode = 0o750
if len(perm) &gt; 0 </span><span class="cov8" title="1">{
mode = perm[0]
}</span>
<span class="cov8" title="1">dir := filepath.Dir(dst)
if err = os.MkdirAll(dir, mode); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if err = os.Chmod(dir, mode); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">out, err := os.Create(dst)
if err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">defer out.Close()
_, err = io.Copy(out, src)
return err</span>
}
// 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" --&gt; JSON binding
// "application/xml" --&gt; 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 <span class="cov8" title="1">{
b := binding.Default(c.Request.Method, c.ContentType())
return c.MustBindWith(obj, b)
}</span>
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.JSON)
}</span>
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) BindXML(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.XML)
}</span>
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.Query)
}</span>
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
func (c *Context) BindYAML(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.YAML)
}</span>
// BindTOML is a shortcut for c.MustBindWith(obj, binding.TOML).
func (c *Context) BindTOML(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.TOML)
}</span>
// BindPlain is a shortcut for c.MustBindWith(obj, binding.Plain).
func (c *Context) BindPlain(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.Plain)
}</span>
// BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).
func (c *Context) BindHeader(obj any) error <span class="cov8" title="1">{
return c.MustBindWith(obj, binding.Header)
}</span>
// 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 <span class="cov8" title="1">{
if err := c.ShouldBindUri(obj); err != nil </span><span class="cov8" title="1">{
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) //nolint: errcheck
return err
}</span>
<span class="cov8" title="1">return nil</span>
}
// 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 <span class="cov8" title="1">{
err := c.ShouldBindWith(obj, b)
if err != nil </span><span class="cov8" title="1">{
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 </span>{
case errors.As(err, &amp;maxBytesErr):<span class="cov8" title="1">
c.AbortWithError(http.StatusRequestEntityTooLarge, err).SetType(ErrorTypeBind)</span> //nolint: errcheck
default:<span class="cov8" title="1">
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind)</span> //nolint: errcheck
}
<span class="cov8" title="1">return err</span>
}
<span class="cov8" title="1">return nil</span>
}
// 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" --&gt; JSON binding
// "application/xml" --&gt; 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 <span class="cov8" title="1">{
b := binding.Default(c.Request.Method, c.ContentType())
return c.ShouldBindWith(obj, b)
}</span>
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.JSON)
}</span>
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) ShouldBindXML(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.XML)
}</span>
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.Query)
}</span>
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
func (c *Context) ShouldBindYAML(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.YAML)
}</span>
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
func (c *Context) ShouldBindTOML(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.TOML)
}</span>
// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).
func (c *Context) ShouldBindPlain(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.Plain)
}</span>
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
func (c *Context) ShouldBindHeader(obj any) error <span class="cov8" title="1">{
return c.ShouldBindWith(obj, binding.Header)
}</span>
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
func (c *Context) ShouldBindUri(obj any) error <span class="cov8" title="1">{
m := make(map[string][]string, len(c.Params))
for _, v := range c.Params </span><span class="cov8" title="1">{
m[v.Key] = []string{v.Value}
}</span>
<span class="cov8" title="1">return binding.Uri.BindUri(m, obj)</span>
}
// 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 <span class="cov8" title="1">{
return b.Bind(c.Request, obj)
}</span>
// 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) <span class="cov8" title="1">{
var body []byte
if cb, ok := c.Get(BodyBytesKey); ok </span><span class="cov8" title="1">{
if cbb, ok := cb.([]byte); ok </span><span class="cov8" title="1">{
body = cbb
}</span>
}
<span class="cov8" title="1">if body == nil </span><span class="cov8" title="1">{
body, err = io.ReadAll(c.Request.Body)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">c.Set(BodyBytesKey, body)</span>
}
<span class="cov8" title="1">return bb.BindBody(body, obj)</span>
}
// ShouldBindBodyWithJSON is a shortcut for c.ShouldBindBodyWith(obj, binding.JSON).
func (c *Context) ShouldBindBodyWithJSON(obj any) error <span class="cov8" title="1">{
return c.ShouldBindBodyWith(obj, binding.JSON)
}</span>
// ShouldBindBodyWithXML is a shortcut for c.ShouldBindBodyWith(obj, binding.XML).
func (c *Context) ShouldBindBodyWithXML(obj any) error <span class="cov8" title="1">{
return c.ShouldBindBodyWith(obj, binding.XML)
}</span>
// ShouldBindBodyWithYAML is a shortcut for c.ShouldBindBodyWith(obj, binding.YAML).
func (c *Context) ShouldBindBodyWithYAML(obj any) error <span class="cov8" title="1">{
return c.ShouldBindBodyWith(obj, binding.YAML)
}</span>
// ShouldBindBodyWithTOML is a shortcut for c.ShouldBindBodyWith(obj, binding.TOML).
func (c *Context) ShouldBindBodyWithTOML(obj any) error <span class="cov8" title="1">{
return c.ShouldBindBodyWith(obj, binding.TOML)
}</span>
// ShouldBindBodyWithPlain is a shortcut for c.ShouldBindBodyWith(obj, binding.Plain).
func (c *Context) ShouldBindBodyWithPlain(obj any) error <span class="cov8" title="1">{
return c.ShouldBindBodyWith(obj, binding.Plain)
}</span>
// 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 <span class="cov8" title="1">{
// Check if we're running on a trusted platform, continue running backwards if error
if c.engine.TrustedPlatform != "" </span><span class="cov8" title="1">{
// Developers can define their own header of Trusted Platform or use predefined constants
if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" </span><span class="cov8" title="1">{
return addr
}</span>
}
// Legacy "AppEngine" flag
<span class="cov8" title="1">if c.engine.AppEngine </span><span class="cov8" title="1">{
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 != "" </span><span class="cov8" title="1">{
return addr
}</span>
}
// 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()
<span class="cov8" title="1">remoteIP := net.ParseIP(c.RemoteIP())
if remoteIP == nil </span><span class="cov8" title="1">{
return ""
}</span>
<span class="cov8" title="1">trusted := c.engine.isTrustedProxy(remoteIP)
if trusted &amp;&amp; c.engine.ForwardedByClientIP &amp;&amp; c.engine.RemoteIPHeaders != nil </span><span class="cov8" title="1">{
for _, headerName := range c.engine.RemoteIPHeaders </span><span class="cov8" title="1">{
ip, valid := c.engine.validateHeader(c.requestHeader(headerName))
if valid </span><span class="cov8" title="1">{
return ip
}</span>
}
}
<span class="cov8" title="1">return remoteIP.String()</span>
}
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
func (c *Context) RemoteIP() string <span class="cov8" title="1">{
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err != nil </span><span class="cov8" title="1">{
return ""
}</span>
<span class="cov8" title="1">return ip</span>
}
// ContentType returns the Content-Type header of the request.
func (c *Context) ContentType() string <span class="cov8" title="1">{
return filterFlags(c.requestHeader("Content-Type"))
}</span>
// IsWebsocket returns true if the request headers indicate that a websocket
// handshake is being initiated by the client.
func (c *Context) IsWebsocket() bool <span class="cov8" title="1">{
if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &amp;&amp;
strings.EqualFold(c.requestHeader("Upgrade"), "websocket") </span><span class="cov8" title="1">{
return true
}</span>
<span class="cov8" title="1">return false</span>
}
func (c *Context) requestHeader(key string) string <span class="cov8" title="1">{
return c.Request.Header.Get(key)
}</span>
/************************************/
/******** RESPONSE RENDERING ********/
/************************************/
// bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.
func bodyAllowedForStatus(status int) bool <span class="cov8" title="1">{
switch </span>{
case status &gt;= 100 &amp;&amp; status &lt;= 199:<span class="cov8" title="1">
return false</span>
case status == http.StatusNoContent:<span class="cov8" title="1">
return false</span>
case status == http.StatusNotModified:<span class="cov8" title="1">
return false</span>
}
<span class="cov8" title="1">return true</span>
}
// Status sets the HTTP response code.
func (c *Context) Status(code int) <span class="cov8" title="1">{
c.Writer.WriteHeader(code)
}</span>
// 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) <span class="cov8" title="1">{
if value == "" </span><span class="cov8" title="1">{
c.Writer.Header().Del(key)
return
}</span>
<span class="cov8" title="1">c.Writer.Header().Set(key, value)</span>
}
// GetHeader returns value from request headers.
func (c *Context) GetHeader(key string) string <span class="cov8" title="1">{
return c.requestHeader(key)
}</span>
// GetRawData returns stream data.
func (c *Context) GetRawData() ([]byte, error) <span class="cov8" title="1">{
if c.Request.Body == nil </span><span class="cov8" title="1">{
return nil, errors.New("cannot read nil body")
}</span>
<span class="cov8" title="1">return io.ReadAll(c.Request.Body)</span>
}
// SetSameSite with cookie
func (c *Context) SetSameSite(samesite http.SameSite) <span class="cov8" title="1">{
c.sameSite = samesite
}</span>
// 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) <span class="cov8" title="1">{
if path == "" </span><span class="cov8" title="1">{
path = "/"
}</span>
<span class="cov8" title="1">http.SetCookie(c.Writer, &amp;http.Cookie{
Name: name,
Value: url.QueryEscape(value),
MaxAge: maxAge,
Path: path,
Domain: domain,
SameSite: c.sameSite,
Secure: secure,
HttpOnly: httpOnly,
})</span>
}
// 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) <span class="cov8" title="1">{
if cookie.Path == "" </span><span class="cov8" title="1">{
cookie.Path = "/"
}</span>
<span class="cov8" title="1">if cookie.SameSite == http.SameSiteDefaultMode </span><span class="cov8" title="1">{
cookie.SameSite = c.sameSite
}</span>
<span class="cov8" title="1">http.SetCookie(c.Writer, cookie)</span>
}
// 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) <span class="cov8" title="1">{
cookie, err := c.Request.Cookie(name)
if err != nil </span><span class="cov8" title="1">{
return "", err
}</span>
<span class="cov8" title="1">val, _ := url.QueryUnescape(cookie.Value)
return val, nil</span>
}
// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) <span class="cov8" title="1">{
c.Status(code)
if !bodyAllowedForStatus(code) </span><span class="cov8" title="1">{
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
return
}</span>
<span class="cov8" title="1">if err := r.Render(c.Writer); err != nil </span><span class="cov8" title="1">{
// Pushing error to c.Errors
_ = c.Error(err)
c.Abort()
}</span>
}
// 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) <span class="cov8" title="1">{
instance := c.engine.HTMLRender.Instance(name, obj)
c.Render(code, instance)
}</span>
// 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) <span class="cov8" title="1">{
c.Render(code, render.IndentedJSON{Data: obj})
}</span>
// 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) <span class="cov8" title="1">{
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
}</span>
// 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) <span class="cov8" title="1">{
callback := c.DefaultQuery("callback", "")
if callback == "" </span><span class="cov8" title="1">{
c.Render(code, render.JSON{Data: obj})
return
}</span>
<span class="cov8" title="1">c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})</span>
}
// 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) <span class="cov8" title="1">{
c.Render(code, render.JSON{Data: obj})
}</span>
// 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) <span class="cov8" title="1">{
c.Render(code, render.AsciiJSON{Data: obj})
}</span>
// 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) <span class="cov8" title="1">{
c.Render(code, render.PureJSON{Data: obj})
}</span>
// 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) <span class="cov8" title="1">{
c.Render(code, render.XML{Data: obj})
}</span>
// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj any) <span class="cov8" title="1">{
c.Render(code, render.YAML{Data: obj})
}</span>
// TOML serializes the given struct as TOML into the response body.
func (c *Context) TOML(code int, obj any) <span class="cov8" title="1">{
c.Render(code, render.TOML{Data: obj})
}</span>
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj any) <span class="cov8" title="1">{
c.Render(code, render.ProtoBuf{Data: obj})
}</span>
// String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...any) <span class="cov8" title="1">{
c.Render(code, render.String{Format: format, Data: values})
}</span>
// Redirect returns an HTTP redirect to the specific location.
func (c *Context) Redirect(code int, location string) <span class="cov8" title="1">{
c.Render(-1, render.Redirect{
Code: code,
Location: location,
Request: c.Request,
})
}</span>
// Data writes some data into the body stream and updates the HTTP code.
func (c *Context) Data(code int, contentType string, data []byte) <span class="cov8" title="1">{
c.Render(code, render.Data{
ContentType: contentType,
Data: data,
})
}</span>
// 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) <span class="cov8" title="1">{
c.Render(code, render.Reader{
Headers: extraHeaders,
ContentType: contentType,
ContentLength: contentLength,
Reader: reader,
})
}</span>
// File writes the specified file into the body stream in an efficient way.
func (c *Context) File(filepath string) <span class="cov8" title="1">{
http.ServeFile(c.Writer, c.Request, filepath)
}</span>
// 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) <span class="cov8" title="1">{
defer func(old string) </span><span class="cov8" title="1">{
c.Request.URL.Path = old
}</span>(c.Request.URL.Path)
<span class="cov8" title="1">c.Request.URL.Path = filepath
http.FileServer(fs).ServeHTTP(c.Writer, c.Request)</span>
}
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
func escapeQuotes(s string) string <span class="cov8" title="1">{
return quoteEscaper.Replace(s)
}</span>
// 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) <span class="cov8" title="1">{
if isASCII(filename) </span><span class="cov8" title="1">{
c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+escapeQuotes(filename)+`"`)
}</span> else<span class="cov8" title="1"> {
c.Writer.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''`+url.QueryEscape(filename))
}</span>
<span class="cov8" title="1">http.ServeFile(c.Writer, c.Request, filepath)</span>
}
// SSEvent writes a Server-Sent Event into the body stream.
func (c *Context) SSEvent(name string, message any) <span class="cov8" title="1">{
c.Render(-1, sse.Event{
Event: name,
Data: message,
})
}</span>
// 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 <span class="cov8" title="1">{
w := c.Writer
clientGone := w.CloseNotify()
for </span><span class="cov8" title="1">{
select </span>{
case &lt;-clientGone:<span class="cov8" title="1">
return true</span>
default:<span class="cov8" title="1">
keepOpen := step(w)
w.Flush()
if !keepOpen </span><span class="cov8" title="1">{
return false
}</span>
}
}
}
/************************************/
/******** 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) <span class="cov8" title="1">{
switch c.NegotiateFormat(config.Offered...) </span>{
case binding.MIMEJSON:<span class="cov8" title="1">
data := chooseData(config.JSONData, config.Data)
c.JSON(code, data)</span>
case binding.MIMEHTML:<span class="cov8" title="1">
data := chooseData(config.HTMLData, config.Data)
c.HTML(code, config.HTMLName, data)</span>
case binding.MIMEXML:<span class="cov8" title="1">
data := chooseData(config.XMLData, config.Data)
c.XML(code, data)</span>
case binding.MIMEYAML, binding.MIMEYAML2:<span class="cov8" title="1">
data := chooseData(config.YAMLData, config.Data)
c.YAML(code, data)</span>
case binding.MIMETOML:<span class="cov8" title="1">
data := chooseData(config.TOMLData, config.Data)
c.TOML(code, data)</span>
default:<span class="cov8" title="1">
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server"))</span> //nolint: errcheck
}
}
// NegotiateFormat returns an acceptable Accept format.
func (c *Context) NegotiateFormat(offered ...string) string <span class="cov8" title="1">{
assert1(len(offered) &gt; 0, "you must provide at least one offer")
if c.Accepted == nil </span><span class="cov8" title="1">{
c.Accepted = parseAccept(c.requestHeader("Accept"))
}</span>
<span class="cov8" title="1">if len(c.Accepted) == 0 </span><span class="cov8" title="1">{
return offered[0]
}</span>
<span class="cov8" title="1">for _, accepted := range c.Accepted </span><span class="cov8" title="1">{
for _, offer := range offered </span><span class="cov8" title="1">{
// 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 &lt; len(accepted) &amp;&amp; i &lt; len(offer); i++ </span><span class="cov8" title="1">{
if accepted[i] == '*' || offer[i] == '*' </span><span class="cov8" title="1">{
return offer
}</span>
<span class="cov8" title="1">if accepted[i] != offer[i] </span><span class="cov8" title="1">{
break</span>
}
}
<span class="cov8" title="1">if i == len(accepted) </span><span class="cov8" title="1">{
return offer
}</span>
}
}
<span class="cov8" title="1">return ""</span>
}
// SetAccepted sets Accept header data.
func (c *Context) SetAccepted(formats ...string) <span class="cov8" title="1">{
c.Accepted = formats
}</span>
/************************************/
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/
// hasRequestContext returns whether c.Request has Context and fallback.
func (c *Context) hasRequestContext() bool <span class="cov8" title="1">{
hasFallback := c.engine != nil &amp;&amp; c.engine.ContextWithFallback
hasRequestContext := c.Request != nil &amp;&amp; c.Request.Context() != nil
return hasFallback &amp;&amp; hasRequestContext
}</span>
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
func (c *Context) Deadline() (deadline time.Time, ok bool) <span class="cov8" title="1">{
if !c.hasRequestContext() </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">return c.Request.Context().Deadline()</span>
}
// Done returns nil (chan which will wait forever) when c.Request has no Context.
func (c *Context) Done() &lt;-chan struct{} <span class="cov8" title="1">{
if !c.hasRequestContext() </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">return c.Request.Context().Done()</span>
}
// Err returns nil when c.Request has no Context.
func (c *Context) Err() error <span class="cov8" title="1">{
if !c.hasRequestContext() </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">return c.Request.Context().Err()</span>
}
// 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 <span class="cov8" title="1">{
if key == ContextRequestKey </span><span class="cov8" title="1">{
return c.Request
}</span>
<span class="cov8" title="1">if key == ContextKey </span><span class="cov8" title="1">{
return c
}</span>
<span class="cov8" title="1">if keyAsString, ok := key.(string); ok </span><span class="cov8" title="1">{
if val, exists := c.Get(keyAsString); exists </span><span class="cov8" title="1">{
return val
}</span>
}
<span class="cov8" title="1">if !c.hasRequestContext() </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">return c.Request.Context().Value(key)</span>
}
</pre>
<pre class="file" id="file18" style="display: none">// 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 <span class="cov8" title="1">{
return atomic.LoadInt32(&amp;ginMode) == debugCode
}</span>
// 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) <span class="cov8" title="1">{
if IsDebugging() </span><span class="cov8" title="1">{
nuHandlers := len(handlers)
handlerName := nameOfFunction(handlers.Last())
if DebugPrintRouteFunc == nil </span><span class="cov8" title="1">{
debugPrint("%-6s %-25s --&gt; %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
}</span> else<span class="cov8" title="1"> {
DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers)
}</span>
}
}
func debugPrintLoadTemplate(tmpl *template.Template) <span class="cov8" title="1">{
if IsDebugging() </span><span class="cov8" title="1">{
var buf strings.Builder
for _, tmpl := range tmpl.Templates() </span><span class="cov8" title="1">{
buf.WriteString("\t- ")
buf.WriteString(tmpl.Name())
buf.WriteString("\n")
}</span>
<span class="cov8" title="1">debugPrint("Loaded HTML Templates (%d): \n%s\n", len(tmpl.Templates()), buf.String())</span>
}
}
func debugPrint(format string, values ...any) <span class="cov8" title="1">{
if !IsDebugging() </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">if DebugPrintFunc != nil </span><span class="cov8" title="1">{
DebugPrintFunc(format, values...)
return
}</span>
<span class="cov8" title="1">if !strings.HasSuffix(format, "\n") </span><span class="cov8" title="1">{
format += "\n"
}</span>
<span class="cov8" title="1">fmt.Fprintf(DefaultWriter, "[GIN-debug] "+format, values...)</span>
}
func getMinVer(v string) (uint64, error) <span class="cov8" title="1">{
first := strings.IndexByte(v, '.')
last := strings.LastIndexByte(v, '.')
if first == last </span><span class="cov8" title="1">{
return strconv.ParseUint(v[first+1:], 10, 64)
}</span>
<span class="cov8" title="1">return strconv.ParseUint(v[first+1:last], 10, 64)</span>
}
func debugPrintWARNINGDefault() <span class="cov8" title="1">{
if v, e := getMinVer(runtime.Version()); e == nil &amp;&amp; v &lt; ginSupportMinGoVer </span><span class="cov0" title="0">{
debugPrint(`[WARNING] Now Gin requires Go 1.23+.
`)
}</span>
<span class="cov8" title="1">debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`)</span>
}
func debugPrintWARNINGNew() <span class="cov8" title="1">{
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
`)
}</span>
func debugPrintWARNINGSetHTMLTemplate() <span class="cov8" title="1">{
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) // &lt;&lt; good place
`)
}</span>
func debugPrintError(err error) <span class="cov8" title="1">{
if err != nil &amp;&amp; IsDebugging() </span><span class="cov8" title="1">{
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
}</span>
}
</pre>
<pre class="file" id="file19" style="display: none">// 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 <span class="cov8" title="1">{
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)
}</span>
</pre>
<pre class="file" id="file20" style="display: none">// 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 &lt;&lt; 63
// ErrorTypeRender is used when Context.Render() fails.
ErrorTypeRender ErrorType = 1 &lt;&lt; 62
// ErrorTypePrivate indicates a private error.
ErrorTypePrivate ErrorType = 1 &lt;&lt; 0
// ErrorTypePublic indicates a public error.
ErrorTypePublic ErrorType = 1 &lt;&lt; 1
// ErrorTypeAny indicates any other error.
ErrorTypeAny ErrorType = 1&lt;&lt;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 <span class="cov8" title="1">{
msg.Type = flags
return msg
}</span>
// SetMeta sets the error's meta data.
func (msg *Error) SetMeta(data any) *Error <span class="cov8" title="1">{
msg.Meta = data
return msg
}</span>
// JSON creates a properly formatted JSON
func (msg *Error) JSON() any <span class="cov8" title="1">{
jsonData := H{}
if msg.Meta != nil </span><span class="cov8" title="1">{
value := reflect.ValueOf(msg.Meta)
switch value.Kind() </span>{
case reflect.Struct:<span class="cov8" title="1">
return msg.Meta</span>
case reflect.Map:<span class="cov8" title="1">
for _, key := range value.MapKeys() </span><span class="cov8" title="1">{
jsonData[key.String()] = value.MapIndex(key).Interface()
}</span>
default:<span class="cov8" title="1">
jsonData["meta"] = msg.Meta</span>
}
}
<span class="cov8" title="1">if _, ok := jsonData["error"]; !ok </span><span class="cov8" title="1">{
jsonData["error"] = msg.Error()
}</span>
<span class="cov8" title="1">return jsonData</span>
}
// MarshalJSON implements the json.Marshaller interface.
func (msg *Error) MarshalJSON() ([]byte, error) <span class="cov8" title="1">{
return json.API.Marshal(msg.JSON())
}</span>
// Error implements the error interface.
func (msg Error) Error() string <span class="cov8" title="1">{
return msg.Err.Error()
}</span>
// IsType judges one error.
func (msg *Error) IsType(flags ErrorType) bool <span class="cov8" title="1">{
return (msg.Type &amp; flags) &gt; 0
}</span>
// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()
func (msg Error) Unwrap() error <span class="cov8" title="1">{
return msg.Err
}</span>
// 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 <span class="cov8" title="1">{
if len(a) == 0 </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">if typ == ErrorTypeAny </span><span class="cov8" title="1">{
return a
}</span>
<span class="cov8" title="1">var result errorMsgs
for _, msg := range a </span><span class="cov8" title="1">{
if msg.IsType(typ) </span><span class="cov8" title="1">{
result = append(result, msg)
}</span>
}
<span class="cov8" title="1">return result</span>
}
// 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 <span class="cov8" title="1">{
if length := len(a); length &gt; 0 </span><span class="cov8" title="1">{
return a[length-1]
}</span>
<span class="cov8" title="1">return nil</span>
}
// 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 <span class="cov8" title="1">{
if len(a) == 0 </span><span class="cov8" title="1">{
return nil
}</span>
<span class="cov8" title="1">errorStrings := make([]string, len(a))
for i, err := range a </span><span class="cov8" title="1">{
errorStrings[i] = err.Error()
}</span>
<span class="cov8" title="1">return errorStrings</span>
}
func (a errorMsgs) JSON() any <span class="cov8" title="1">{
switch length := len(a); length </span>{
case 0:<span class="cov8" title="1">
return nil</span>
case 1:<span class="cov8" title="1">
return a.Last().JSON()</span>
default:<span class="cov8" title="1">
jsonData := make([]any, length)
for i, err := range a </span><span class="cov8" title="1">{
jsonData[i] = err.JSON()
}</span>
<span class="cov8" title="1">return jsonData</span>
}
}
// MarshalJSON implements the json.Marshaller interface.
func (a errorMsgs) MarshalJSON() ([]byte, error) <span class="cov8" title="1">{
return json.API.Marshal(a.JSON())
}</span>
func (a errorMsgs) String() string <span class="cov8" title="1">{
if len(a) == 0 </span><span class="cov8" title="1">{
return ""
}</span>
<span class="cov8" title="1">var buffer strings.Builder
for i, msg := range a </span><span class="cov8" title="1">{
fmt.Fprintf(&amp;buffer, "Error #%02d: %s\n", i+1, msg.Err)
if msg.Meta != nil </span><span class="cov8" title="1">{
fmt.Fprintf(&amp;buffer, " Meta: %v\n", msg.Meta)
}</span>
}
<span class="cov8" title="1">return buffer.String()</span>
}
</pre>
<pre class="file" id="file21" style="display: none">// 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) <span class="cov8" title="1">{
f, err := o.FileSystem.Open(name)
if err != nil </span><span class="cov8" title="1">{
return nil, err
}</span>
<span class="cov8" title="1">return neutralizedReaddirFile{f}, nil</span>
}
// 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) <span class="cov8" title="1">{
// this disables directory listing
return nil, nil
}</span>
// 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 <span class="cov8" title="1">{
fs := http.Dir(root)
if listDirectory </span><span class="cov8" title="1">{
return fs
}</span>
<span class="cov8" title="1">return &amp;OnlyFilesFS{FileSystem: fs}</span>
}
</pre>
<pre class="file" id="file22" style="display: none">// 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 &lt;&lt; 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 <span class="cov8" title="1">{
if length := len(c); length &gt; 0 </span><span class="cov8" title="1">{
return c[length-1]
}</span>
<span class="cov8" title="1">return nil</span>
}
// 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 <span class="cov8" title="1">{
debugPrintWARNINGNew()
engine := &amp;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 </span><span class="cov8" title="1">{
return engine.allocateContext(engine.maxParams)
}</span>
<span class="cov8" title="1">return engine.With(opts...)</span>
}
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default(opts ...OptionFunc) *Engine <span class="cov8" title="1">{
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}</span>
func (engine *Engine) Handler() http.Handler <span class="cov8" title="1">{
if !engine.UseH2C </span><span class="cov8" title="1">{
return engine
}</span>
<span class="cov8" title="1">h2s := &amp;http2.Server{}
return h2c.NewHandler(engine, h2s)</span>
}
func (engine *Engine) allocateContext(maxParams uint16) *Context <span class="cov8" title="1">{
v := make(Params, 0, maxParams)
skippedNodes := make([]skippedNode, 0, engine.maxSections)
return &amp;Context{engine: engine, params: &amp;v, skippedNodes: &amp;skippedNodes}
}</span>
// Delims sets template left and right delims and returns an Engine instance.
func (engine *Engine) Delims(left, right string) *Engine <span class="cov8" title="1">{
engine.delims = render.Delims{Left: left, Right: right}
return engine
}</span>
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine <span class="cov8" title="1">{
engine.secureJSONPrefix = prefix
return engine
}</span>
// LoadHTMLGlob loads HTML files identified by glob pattern
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLGlob(pattern string) <span class="cov8" title="1">{
left := engine.delims.Left
right := engine.delims.Right
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
if IsDebugging() </span><span class="cov8" title="1">{
debugPrintLoadTemplate(templ)
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
return
}</span>
<span class="cov8" title="1">engine.SetHTMLTemplate(templ)</span>
}
// LoadHTMLFiles loads a slice of HTML files
// and associates the result with HTML renderer.
func (engine *Engine) LoadHTMLFiles(files ...string) <span class="cov8" title="1">{
if IsDebugging() </span><span class="cov8" title="1">{
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
return
}</span>
<span class="cov8" title="1">templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
engine.SetHTMLTemplate(templ)</span>
}
// 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) <span class="cov8" title="1">{
if IsDebugging() </span><span class="cov8" title="1">{
engine.HTMLRender = render.HTMLDebug{FileSystem: fs, Patterns: patterns, FuncMap: engine.FuncMap, Delims: engine.delims}
return
}</span>
<span class="cov8" title="1">templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFS(
filesystem.FileSystem{FileSystem: fs}, patterns...))
engine.SetHTMLTemplate(templ)</span>
}
// SetHTMLTemplate associate a template with HTML renderer.
func (engine *Engine) SetHTMLTemplate(templ *template.Template) <span class="cov8" title="1">{
if len(engine.trees) &gt; 0 </span><span class="cov8" title="1">{
debugPrintWARNINGSetHTMLTemplate()
}</span>
<span class="cov8" title="1">engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}</span>
}
// SetFuncMap sets the FuncMap used for template.FuncMap.
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) <span class="cov8" title="1">{
engine.FuncMap = funcMap
}</span>
// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
func (engine *Engine) NoRoute(handlers ...HandlerFunc) <span class="cov8" title="1">{
engine.noRoute = handlers
engine.rebuild404Handlers()
}</span>
// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) <span class="cov8" title="1">{
engine.noMethod = handlers
engine.rebuild405Handlers()
}</span>
// 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 <span class="cov8" title="1">{
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}</span>
// With returns an Engine with the configuration set in the OptionFunc.
func (engine *Engine) With(opts ...OptionFunc) *Engine <span class="cov8" title="1">{
for _, opt := range opts </span><span class="cov8" title="1">{
opt(engine)
}</span>
<span class="cov8" title="1">return engine</span>
}
func (engine *Engine) rebuild404Handlers() <span class="cov8" title="1">{
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
}</span>
func (engine *Engine) rebuild405Handlers() <span class="cov8" title="1">{
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
}</span>
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) <span class="cov8" title="1">{
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) &gt; 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers)
root := engine.trees.get(method)
if root == nil </span><span class="cov8" title="1">{
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}</span>
<span class="cov8" title="1">root.addRoute(path, handlers)
if paramsCount := countParams(path); paramsCount &gt; engine.maxParams </span><span class="cov8" title="1">{
engine.maxParams = paramsCount
}</span>
<span class="cov8" title="1">if sectionsCount := countSections(path); sectionsCount &gt; engine.maxSections </span><span class="cov8" title="1">{
engine.maxSections = sectionsCount
}</span>
}
// 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) <span class="cov8" title="1">{
for _, tree := range engine.trees </span><span class="cov8" title="1">{
routes = iterate("", tree.method, routes, tree.root)
}</span>
<span class="cov8" title="1">return routes</span>
}
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo <span class="cov8" title="1">{
path += root.path
if len(root.handlers) &gt; 0 </span><span class="cov8" title="1">{
handlerFunc := root.handlers.Last()
routes = append(routes, RouteInfo{
Method: method,
Path: path,
Handler: nameOfFunction(handlerFunc),
HandlerFunc: handlerFunc,
})
}</span>
<span class="cov8" title="1">for _, child := range root.children </span><span class="cov8" title="1">{
routes = iterate(path, method, routes, child)
}</span>
<span class="cov8" title="1">return routes</span>
}
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) <span class="cov8" title="1">{
if engine.trustedProxies == nil </span><span class="cov8" title="1">{
return nil, nil
}</span>
<span class="cov8" title="1">cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
for _, trustedProxy := range engine.trustedProxies </span><span class="cov8" title="1">{
if !strings.Contains(trustedProxy, "/") </span><span class="cov8" title="1">{
ip := parseIP(trustedProxy)
if ip == nil </span><span class="cov8" title="1">{
return cidr, &amp;net.ParseError{Type: "IP address", Text: trustedProxy}
}</span>
<span class="cov8" title="1">switch len(ip) </span>{
case net.IPv4len:<span class="cov8" title="1">
trustedProxy += "/32"</span>
case net.IPv6len:<span class="cov8" title="1">
trustedProxy += "/128"</span>
}
}
<span class="cov8" title="1">_, cidrNet, err := net.ParseCIDR(trustedProxy)
if err != nil </span><span class="cov8" title="1">{
return cidr, err
}</span>
<span class="cov8" title="1">cidr = append(cidr, cidrNet)</span>
}
<span class="cov8" title="1">return cidr, nil</span>
}
// 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 <span class="cov8" title="1">{
engine.trustedProxies = trustedProxies
return engine.parseTrustedProxies()
}</span>
// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
func (engine *Engine) isUnsafeTrustedProxies() bool <span class="cov8" title="1">{
return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
}</span>
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
func (engine *Engine) parseTrustedProxies() error <span class="cov8" title="1">{
trustedCIDRs, err := engine.prepareTrustedCIDRs()
engine.trustedCIDRs = trustedCIDRs
return err
}</span>
// 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 <span class="cov8" title="1">{
if engine.trustedCIDRs == nil </span><span class="cov8" title="1">{
return false
}</span>
<span class="cov8" title="1">for _, cidr := range engine.trustedCIDRs </span><span class="cov8" title="1">{
if cidr.Contains(ip) </span><span class="cov8" title="1">{
return true
}</span>
}
<span class="cov8" title="1">return false</span>
}
// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) <span class="cov8" title="1">{
if header == "" </span><span class="cov8" title="1">{
return "", false
}</span>
<span class="cov8" title="1">items := strings.Split(header, ",")
for i := len(items) - 1; i &gt;= 0; i-- </span><span class="cov8" title="1">{
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil </span><span class="cov8" title="1">{
break</span>
}
// X-Forwarded-For is appended by proxy
// Check IPs in reverse order and stop when find untrusted proxy
<span class="cov8" title="1">if (i == 0) || (!engine.isTrustedProxy(ip)) </span><span class="cov8" title="1">{
return ipStr, true
}</span>
}
<span class="cov8" title="1">return "", false</span>
}
// updateRouteTree do update to the route tree recursively
func updateRouteTree(n *node) <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">for _, child := range n.children </span><span class="cov8" title="1">{
updateRouteTree(child)
}</span>
}
// updateRouteTrees do update to the route trees
func (engine *Engine) updateRouteTrees() <span class="cov8" title="1">{
for _, tree := range engine.trees </span><span class="cov8" title="1">{
updateRouteTree(tree.root)
}</span>
}
// 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 <span class="cov8" title="1">{
parsedIP := net.ParseIP(ip)
if ipv4 := parsedIP.To4(); ipv4 != nil </span><span class="cov8" title="1">{
// return ip in a 4-byte representation
return ipv4
}</span>
// return ip in a 16-byte representation or nil
<span class="cov8" title="1">return parsedIP</span>
}
// 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) <span class="cov8" title="1">{
defer func() </span><span class="cov8" title="1">{ debugPrintError(err) }</span>()
<span class="cov8" title="1">if engine.isUnsafeTrustedProxies() </span><span class="cov8" title="1">{
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.")
}</span>
<span class="cov8" title="1">engine.updateRouteTrees()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler())
return</span>
}
// 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) <span class="cov8" title="1">{
debugPrint("Listening and serving HTTPS on %s\n", addr)
defer func() </span><span class="cov8" title="1">{ debugPrintError(err) }</span>()
<span class="cov8" title="1">if engine.isUnsafeTrustedProxies() </span><span class="cov8" title="1">{
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.")
}</span>
<span class="cov8" title="1">err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
return</span>
}
// 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) <span class="cov8" title="1">{
debugPrint("Listening and serving HTTP on unix:/%s", file)
defer func() </span><span class="cov8" title="1">{ debugPrintError(err) }</span>()
<span class="cov8" title="1">if engine.isUnsafeTrustedProxies() </span><span class="cov8" title="1">{
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.")
}</span>
<span class="cov8" title="1">listener, err := net.Listen("unix", file)
if err != nil </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">defer listener.Close()
defer os.Remove(file)
err = http.Serve(listener, engine.Handler())
return</span>
}
// 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) <span class="cov8" title="1">{
debugPrint("Listening and serving HTTP on fd@%d", fd)
defer func() </span><span class="cov8" title="1">{ debugPrintError(err) }</span>()
<span class="cov8" title="1">if engine.isUnsafeTrustedProxies() </span><span class="cov8" title="1">{
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.")
}</span>
<span class="cov8" title="1">f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
listener, err := net.FileListener(f)
if err != nil </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">defer listener.Close()
err = engine.RunListener(listener)
return</span>
}
// 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) <span class="cov8" title="1">{
debugPrint("Listening and serving QUIC on %s\n", addr)
defer func() </span><span class="cov8" title="1">{ debugPrintError(err) }</span>()
<span class="cov8" title="1">if engine.isUnsafeTrustedProxies() </span><span class="cov8" title="1">{
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.")
}</span>
<span class="cov8" title="1">err = http3.ListenAndServeQUIC(addr, certFile, keyFile, engine.Handler())
return</span>
}
// 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) <span class="cov8" title="1">{
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
defer func() </span><span class="cov8" title="1">{ debugPrintError(err) }</span>()
<span class="cov8" title="1">if engine.isUnsafeTrustedProxies() </span><span class="cov8" title="1">{
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.")
}</span>
<span class="cov8" title="1">err = http.Serve(listener, engine.Handler())
return</span>
}
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) <span class="cov8" title="1">{
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}</span>
// 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) <span class="cov8" title="1">{
oldIndexValue := c.index
oldHandlers := c.handlers
c.reset()
engine.handleHTTPRequest(c)
c.index = oldIndexValue
c.handlers = oldHandlers
}</span>
func (engine *Engine) handleHTTPRequest(c *Context) <span class="cov8" title="1">{
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if engine.UseRawPath &amp;&amp; len(c.Request.URL.RawPath) &gt; 0 </span><span class="cov8" title="1">{
rPath = c.Request.URL.RawPath
unescape = engine.UnescapePathValues
}</span>
<span class="cov8" title="1">if engine.RemoveExtraSlash </span><span class="cov8" title="1">{
rPath = cleanPath(rPath)
}</span>
// Find root of the tree for the given HTTP method
<span class="cov8" title="1">t := engine.trees
for i, tl := 0, len(t); i &lt; tl; i++ </span><span class="cov8" title="1">{
if t[i].method != httpMethod </span><span class="cov8" title="1">{
continue</span>
}
<span class="cov8" title="1">root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil </span><span class="cov8" title="1">{
c.Params = *value.params
}</span>
<span class="cov8" title="1">if value.handlers != nil </span><span class="cov8" title="1">{
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}</span>
<span class="cov8" title="1">if httpMethod != http.MethodConnect &amp;&amp; rPath != "/" </span><span class="cov8" title="1">{
if value.tsr &amp;&amp; engine.RedirectTrailingSlash </span><span class="cov8" title="1">{
redirectTrailingSlash(c)
return
}</span>
<span class="cov8" title="1">if engine.RedirectFixedPath &amp;&amp; redirectFixedPath(c, root, engine.RedirectFixedPath) </span><span class="cov8" title="1">{
return
}</span>
}
<span class="cov8" title="1">break</span>
}
<span class="cov8" title="1">if engine.HandleMethodNotAllowed &amp;&amp; len(t) &gt; 0 </span><span class="cov8" title="1">{
// 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 </span><span class="cov8" title="1">{
if tree.method == httpMethod </span><span class="cov8" title="1">{
continue</span>
}
<span class="cov8" title="1">if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil </span><span class="cov8" title="1">{
allowed = append(allowed, tree.method)
}</span>
}
<span class="cov8" title="1">if len(allowed) &gt; 0 </span><span class="cov8" title="1">{
c.handlers = engine.allNoMethod
c.writermem.Header().Set("Allow", strings.Join(allowed, ", "))
serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}</span>
}
<span class="cov8" title="1">c.handlers = engine.allNoRoute
serveError(c, http.StatusNotFound, default404Body)</span>
}
var mimePlain = []string{MIMEPlain}
func serveError(c *Context, code int, defaultMessage []byte) <span class="cov8" title="1">{
c.writermem.status = code
c.Next()
if c.writermem.Written() </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">if c.writermem.Status() == code </span><span class="cov8" title="1">{
c.writermem.Header()["Content-Type"] = mimePlain
_, err := c.Writer.Write(defaultMessage)
if err != nil </span><span class="cov0" title="0">{
debugPrint("cannot write message to writer during serve error: %v", err)
}</span>
<span class="cov8" title="1">return</span>
}
<span class="cov8" title="1">c.writermem.WriteHeaderNow()</span>
}
func redirectTrailingSlash(c *Context) <span class="cov8" title="1">{
req := c.Request
p := req.URL.Path
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." </span><span class="cov8" title="1">{
prefix = regSafePrefix.ReplaceAllString(prefix, "")
prefix = regRemoveRepeatedChar.ReplaceAllString(prefix, "/")
p = prefix + "/" + req.URL.Path
}</span>
<span class="cov8" title="1">req.URL.Path = p + "/"
if length := len(p); length &gt; 1 &amp;&amp; p[length-1] == '/' </span><span class="cov8" title="1">{
req.URL.Path = p[:length-1]
}</span>
<span class="cov8" title="1">redirectRequest(c)</span>
}
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool <span class="cov8" title="1">{
req := c.Request
rPath := req.URL.Path
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok </span><span class="cov8" title="1">{
req.URL.Path = bytesconv.BytesToString(fixedPath)
redirectRequest(c)
return true
}</span>
<span class="cov8" title="1">return false</span>
}
func redirectRequest(c *Context) <span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
code = http.StatusTemporaryRedirect
}</span>
<span class="cov8" title="1">debugPrint("redirecting request %d: %s --&gt; %s", code, rPath, rURL)
http.Redirect(c.Writer, req, rURL, code)
c.writermem.WriteHeaderNow()</span>
}
</pre>
<pre class="file" id="file23" style="display: none">// 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 <span class="cov0" title="0">{
once.Do(func() </span><span class="cov0" title="0">{
internalEngine = gin.Default()
}</span>)
<span class="cov0" title="0">return internalEngine</span>
}
// LoadHTMLGlob is a wrapper for Engine.LoadHTMLGlob.
func LoadHTMLGlob(pattern string) <span class="cov0" title="0">{
engine().LoadHTMLGlob(pattern)
}</span>
// LoadHTMLFiles is a wrapper for Engine.LoadHTMLFiles.
func LoadHTMLFiles(files ...string) <span class="cov0" title="0">{
engine().LoadHTMLFiles(files...)
}</span>
// LoadHTMLFS is a wrapper for Engine.LoadHTMLFS.
func LoadHTMLFS(fs http.FileSystem, patterns ...string) <span class="cov0" title="0">{
engine().LoadHTMLFS(fs, patterns...)
}</span>
// SetHTMLTemplate is a wrapper for Engine.SetHTMLTemplate.
func SetHTMLTemplate(templ *template.Template) <span class="cov0" title="0">{
engine().SetHTMLTemplate(templ)
}</span>
// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
func NoRoute(handlers ...gin.HandlerFunc) <span class="cov0" title="0">{
engine().NoRoute(handlers...)
}</span>
// NoMethod is a wrapper for Engine.NoMethod.
func NoMethod(handlers ...gin.HandlerFunc) <span class="cov0" title="0">{
engine().NoMethod(handlers...)
}</span>
// 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 <span class="cov0" title="0">{
return engine().Group(relativePath, handlers...)
}</span>
// Handle is a wrapper for Engine.Handle.
func Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().Handle(httpMethod, relativePath, handlers...)
}</span>
// POST is a shortcut for router.Handle("POST", path, handle)
func POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().POST(relativePath, handlers...)
}</span>
// GET is a shortcut for router.Handle("GET", path, handle)
func GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().GET(relativePath, handlers...)
}</span>
// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().DELETE(relativePath, handlers...)
}</span>
// PATCH is a shortcut for router.Handle("PATCH", path, handle)
func PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().PATCH(relativePath, handlers...)
}</span>
// PUT is a shortcut for router.Handle("PUT", path, handle)
func PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().PUT(relativePath, handlers...)
}</span>
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle)
func OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().OPTIONS(relativePath, handlers...)
}</span>
// HEAD is a shortcut for router.Handle("HEAD", path, handle)
func HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().HEAD(relativePath, handlers...)
}</span>
// Any is a wrapper for Engine.Any.
func Any(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes <span class="cov0" title="0">{
return engine().Any(relativePath, handlers...)
}</span>
// StaticFile is a wrapper for Engine.StaticFile.
func StaticFile(relativePath, filepath string) gin.IRoutes <span class="cov0" title="0">{
return engine().StaticFile(relativePath, filepath)
}</span>
// 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 <span class="cov0" title="0">{
return engine().Static(relativePath, root)
}</span>
// StaticFS is a wrapper for Engine.StaticFS.
func StaticFS(relativePath string, fs http.FileSystem) gin.IRoutes <span class="cov0" title="0">{
return engine().StaticFS(relativePath, fs)
}</span>
// 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 <span class="cov0" title="0">{
return engine().Use(middlewares...)
}</span>
// Routes returns a slice of registered routes.
func Routes() gin.RoutesInfo <span class="cov0" title="0">{
return engine().Routes()
}</span>
// 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) <span class="cov0" title="0">{
return engine().Run(addr...)
}</span>
// 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) <span class="cov0" title="0">{
return engine().RunTLS(addr, certFile, keyFile)
}</span>
// 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) <span class="cov0" title="0">{
return engine().RunUnix(file)
}</span>
// 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) <span class="cov0" title="0">{
return engine().RunFd(fd)
}</span>
</pre>
<pre class="file" id="file24" style="display: none">// 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 <span class="cov8" title="1">{
return unsafe.Slice(unsafe.StringData(s), len(s))
}</span>
// 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 <span class="cov8" title="1">{
return unsafe.String(unsafe.SliceData(b), len(b))
}</span>
</pre>
<pre class="file" id="file25" style="display: none">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) <span class="cov8" title="1">{
f, err := o.FileSystem.Open(name)
if err != nil </span><span class="cov8" title="1">{
return nil, err
}</span>
<span class="cov8" title="1">return fs.File(f), nil</span>
}
</pre>
<pre class="file" id="file26" style="display: none">// 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 <span class="cov8" title="1">{
code := p.StatusCode
switch </span>{
case code &gt;= http.StatusContinue &amp;&amp; code &lt; http.StatusOK:<span class="cov8" title="1">
return white</span>
case code &gt;= http.StatusOK &amp;&amp; code &lt; http.StatusMultipleChoices:<span class="cov8" title="1">
return green</span>
case code &gt;= http.StatusMultipleChoices &amp;&amp; code &lt; http.StatusBadRequest:<span class="cov8" title="1">
return white</span>
case code &gt;= http.StatusBadRequest &amp;&amp; code &lt; http.StatusInternalServerError:<span class="cov8" title="1">
return yellow</span>
default:<span class="cov8" title="1">
return red</span>
}
}
// MethodColor is the ANSI color for appropriately logging http method to a terminal.
func (p *LogFormatterParams) MethodColor() string <span class="cov8" title="1">{
method := p.Method
switch method </span>{
case http.MethodGet:<span class="cov8" title="1">
return blue</span>
case http.MethodPost:<span class="cov8" title="1">
return cyan</span>
case http.MethodPut:<span class="cov8" title="1">
return yellow</span>
case http.MethodDelete:<span class="cov8" title="1">
return red</span>
case http.MethodPatch:<span class="cov8" title="1">
return green</span>
case http.MethodHead:<span class="cov8" title="1">
return magenta</span>
case http.MethodOptions:<span class="cov8" title="1">
return white</span>
default:<span class="cov8" title="1">
return reset</span>
}
}
// ResetColor resets all escape attributes.
func (p *LogFormatterParams) ResetColor() string <span class="cov8" title="1">{
return reset
}</span>
// IsOutputColor indicates whether can colors be outputted to the log.
func (p *LogFormatterParams) IsOutputColor() bool <span class="cov8" title="1">{
return consoleColorMode == forceColor || (consoleColorMode == autoColor &amp;&amp; p.isTerm)
}</span>
// defaultLogFormatter is the default log format function Logger middleware uses.
var defaultLogFormatter = func(param LogFormatterParams) string <span class="cov8" title="1">{
var statusColor, methodColor, resetColor string
if param.IsOutputColor() </span><span class="cov8" title="1">{
statusColor = param.StatusCodeColor()
methodColor = param.MethodColor()
resetColor = param.ResetColor()
}</span>
<span class="cov8" title="1">if param.Latency &gt; time.Minute </span><span class="cov8" title="1">{
param.Latency = param.Latency.Truncate(time.Second)
}</span>
<span class="cov8" title="1">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,
)</span>
}
// DisableConsoleColor disables color output in the console.
func DisableConsoleColor() <span class="cov8" title="1">{
consoleColorMode = disableColor
}</span>
// ForceConsoleColor force color output in the console.
func ForceConsoleColor() <span class="cov8" title="1">{
consoleColorMode = forceColor
}</span>
// ErrorLogger returns a HandlerFunc for any error type.
func ErrorLogger() HandlerFunc <span class="cov8" title="1">{
return ErrorLoggerT(ErrorTypeAny)
}</span>
// ErrorLoggerT returns a HandlerFunc for a given error type.
func ErrorLoggerT(typ ErrorType) HandlerFunc <span class="cov8" title="1">{
return func(c *Context) </span><span class="cov8" title="1">{
c.Next()
errors := c.Errors.ByType(typ)
if len(errors) &gt; 0 </span><span class="cov8" title="1">{
c.JSON(-1, errors)
}</span>
}
}
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
// By default, gin.DefaultWriter = os.Stdout.
func Logger() HandlerFunc <span class="cov8" title="1">{
return LoggerWithConfig(LoggerConfig{})
}</span>
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
func LoggerWithFormatter(f LogFormatter) HandlerFunc <span class="cov8" title="1">{
return LoggerWithConfig(LoggerConfig{
Formatter: f,
})
}</span>
// 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 <span class="cov8" title="1">{
return LoggerWithConfig(LoggerConfig{
Output: out,
SkipPaths: notlogged,
})
}</span>
// LoggerWithConfig instance a Logger middleware with config.
func LoggerWithConfig(conf LoggerConfig) HandlerFunc <span class="cov8" title="1">{
formatter := conf.Formatter
if formatter == nil </span><span class="cov8" title="1">{
formatter = defaultLogFormatter
}</span>
<span class="cov8" title="1">out := conf.Output
if out == nil </span><span class="cov8" title="1">{
out = DefaultWriter
}</span>
<span class="cov8" title="1">notlogged := conf.SkipPaths
isTerm := true
if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
(!isatty.IsTerminal(w.Fd()) &amp;&amp; !isatty.IsCygwinTerminal(w.Fd())) </span><span class="cov8" title="1">{
isTerm = false
}</span>
<span class="cov8" title="1">var skip map[string]struct{}
if length := len(notlogged); length &gt; 0 </span><span class="cov8" title="1">{
skip = make(map[string]struct{}, length)
for _, path := range notlogged </span><span class="cov8" title="1">{
skip[path] = struct{}{}
}</span>
}
<span class="cov8" title="1">return func(c *Context) </span><span class="cov8" title="1">{
// 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 &amp;&amp; conf.Skip(c)) </span><span class="cov8" title="1">{
return
}</span>
<span class="cov8" title="1">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 != "" </span><span class="cov8" title="1">{
path = path + "?" + raw
}</span>
<span class="cov8" title="1">param.Path = path
fmt.Fprint(out, formatter(param))</span>
}
}
</pre>
<pre class="file" id="file27" style="display: none">// 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() <span class="cov8" title="1">{
mode := os.Getenv(EnvGinMode)
SetMode(mode)
}</span>
// SetMode sets gin mode according to input string.
func SetMode(value string) <span class="cov8" title="1">{
if value == "" </span><span class="cov8" title="1">{
if flag.Lookup("test.v") != nil </span><span class="cov8" title="1">{
value = TestMode
}</span> else<span class="cov8" title="1"> {
value = DebugMode
}</span>
}
<span class="cov8" title="1">switch value </span>{
case DebugMode, "":<span class="cov8" title="1">
atomic.StoreInt32(&amp;ginMode, debugCode)</span>
case ReleaseMode:<span class="cov8" title="1">
atomic.StoreInt32(&amp;ginMode, releaseCode)</span>
case TestMode:<span class="cov8" title="1">
atomic.StoreInt32(&amp;ginMode, testCode)</span>
default:<span class="cov8" title="1">
panic("gin mode unknown: " + value + " (available mode: debug release test)")</span>
}
<span class="cov8" title="1">modeName.Store(value)</span>
}
// DisableBindValidation closes the default validator.
func DisableBindValidation() <span class="cov8" title="1">{
binding.Validator = nil
}</span>
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to
// call the UseNumber method on the JSON Decoder instance.
func EnableJsonDecoderUseNumber() <span class="cov8" title="1">{
binding.EnableDecoderUseNumber = true
}</span>
// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
// call the DisallowUnknownFields method on the JSON Decoder instance.
func EnableJsonDecoderDisallowUnknownFields() <span class="cov8" title="1">{
binding.EnableDecoderDisallowUnknownFields = true
}</span>
// Mode returns current gin mode.
func Mode() string <span class="cov8" title="1">{
return modeName.Load().(string)
}</span>
</pre>
<pre class="file" id="file28" style="display: none">// 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 <span class="cov8" title="1">{
const stackBufSize = 128
// Turn empty string into "/"
if p == "" </span><span class="cov8" title="1">{
return "/"
}</span>
// Reasonably sized buffer on stack to avoid allocations in the common case.
// If a larger buffer is required, it gets allocated dynamically.
<span class="cov8" title="1">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] != '/' </span><span class="cov8" title="1">{
r = 0
if n+1 &gt; stackBufSize </span><span class="cov8" title="1">{
buf = make([]byte, n+1)
}</span> else<span class="cov8" title="1"> {
buf = buf[:n+1]
}</span>
<span class="cov8" title="1">buf[0] = '/'</span>
}
<span class="cov8" title="1">trailing := n &gt; 1 &amp;&amp; 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 &lt; n </span><span class="cov8" title="1">{
switch </span>{
case p[r] == '/':<span class="cov8" title="1">
// empty path element, trailing slash is added after the end
r++</span>
case p[r] == '.' &amp;&amp; r+1 == n:<span class="cov8" title="1">
trailing = true
r++</span>
case p[r] == '.' &amp;&amp; p[r+1] == '/':<span class="cov8" title="1">
// . element
r += 2</span>
case p[r] == '.' &amp;&amp; p[r+1] == '.' &amp;&amp; (r+2 == n || p[r+2] == '/'):<span class="cov8" title="1">
// .. element: remove to last /
r += 3
if w &gt; 1 </span><span class="cov8" title="1">{
// can backtrack
w--
if len(buf) == 0 </span><span class="cov8" title="1">{
for w &gt; 1 &amp;&amp; p[w] != '/' </span><span class="cov8" title="1">{
w--
}</span>
} else<span class="cov8" title="1"> {
for w &gt; 1 &amp;&amp; buf[w] != '/' </span><span class="cov8" title="1">{
w--
}</span>
}
}
default:<span class="cov8" title="1">
// Real path element.
// Add slash if needed
if w &gt; 1 </span><span class="cov8" title="1">{
bufApp(&amp;buf, p, w, '/')
w++
}</span>
// Copy element
<span class="cov8" title="1">for r &lt; n &amp;&amp; p[r] != '/' </span><span class="cov8" title="1">{
bufApp(&amp;buf, p, w, p[r])
w++
r++
}</span>
}
}
// Re-append trailing slash
<span class="cov8" title="1">if trailing &amp;&amp; w &gt; 1 </span><span class="cov8" title="1">{
bufApp(&amp;buf, p, w, '/')
w++
}</span>
// 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.
<span class="cov8" title="1">if len(buf) == 0 </span><span class="cov8" title="1">{
return p[:w]
}</span>
<span class="cov8" title="1">return string(buf[:w])</span>
}
// 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) <span class="cov8" title="1">{
b := *buf
if len(b) == 0 </span><span class="cov8" title="1">{
// 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 </span><span class="cov8" title="1">{
return
}</span>
// Otherwise use either the stack buffer, if it is large enough, or
// allocate a new buffer on the heap, and copy all previous characters.
<span class="cov8" title="1">length := len(s)
if length &gt; cap(b) </span><span class="cov8" title="1">{
*buf = make([]byte, length)
}</span> else<span class="cov8" title="1"> {
*buf = (*buf)[:length]
}</span>
<span class="cov8" title="1">b = *buf
copy(b, s[:w])</span>
}
<span class="cov8" title="1">b[w] = c</span>
}
</pre>
<pre class="file" id="file29" style="display: none">// 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 <span class="cov8" title="1">{
return RecoveryWithWriter(DefaultErrorWriter)
}</span>
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
func CustomRecovery(handle RecoveryFunc) HandlerFunc <span class="cov8" title="1">{
return RecoveryWithWriter(DefaultErrorWriter, handle)
}</span>
// 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 <span class="cov8" title="1">{
if len(recovery) &gt; 0 </span><span class="cov8" title="1">{
return CustomRecoveryWithWriter(out, recovery[0])
}</span>
<span class="cov8" title="1">return CustomRecoveryWithWriter(out, defaultHandleRecovery)</span>
}
// 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 <span class="cov8" title="1">{
var logger *log.Logger
if out != nil </span><span class="cov8" title="1">{
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
}</span>
<span class="cov8" title="1">return func(c *Context) </span><span class="cov8" title="1">{
defer func() </span><span class="cov8" title="1">{
if err := recover(); err != nil </span><span class="cov8" title="1">{
// 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 </span><span class="cov8" title="1">{
var se *os.SyscallError
if errors.As(ne, &amp;se) </span><span class="cov8" title="1">{
seStr := strings.ToLower(se.Error())
if strings.Contains(seStr, "broken pipe") ||
strings.Contains(seStr, "connection reset by peer") </span><span class="cov8" title="1">{
brokenPipe = true
}</span>
}
}
<span class="cov8" title="1">if logger != nil </span><span class="cov8" title="1">{
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 </span><span class="cov8" title="1">{
logger.Printf("%s\n%s%s", err, headersToStr, reset)
}</span> else<span class="cov8" title="1"> if IsDebugging() </span><span class="cov8" title="1">{
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
timeFormat(time.Now()), headersToStr, err, stack, reset)
}</span> else<span class="cov8" title="1"> {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
timeFormat(time.Now()), err, stack, reset)
}</span>
}
<span class="cov8" title="1">if brokenPipe </span><span class="cov8" title="1">{
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) //nolint: errcheck
c.Abort()
}</span> else<span class="cov8" title="1"> {
handle(c, err)
}</span>
}
}()
<span class="cov8" title="1">c.Next()</span>
}
}
func defaultHandleRecovery(c *Context, _ any) <span class="cov8" title="1">{
c.AbortWithStatus(http.StatusInternalServerError)
}</span>
// stack returns a nicely formatted stack frame, skipping skip frames.
func stack(skip int) []byte <span class="cov8" title="1">{
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++ </span><span class="cov8" title="1">{ // Skip the expected number of frames
pc, file, line, ok := runtime.Caller(i)
if !ok </span><span class="cov8" title="1">{
break</span>
}
// Print this much at least. If we can't find the source, it won't show.
<span class="cov8" title="1">fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
if file != lastFile </span><span class="cov8" title="1">{
data, err := os.ReadFile(file)
if err != nil </span><span class="cov0" title="0">{
continue</span>
}
<span class="cov8" title="1">lines = bytes.Split(data, []byte{'\n'})
lastFile = file</span>
}
<span class="cov8" title="1">fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))</span>
}
<span class="cov8" title="1">return buf.Bytes()</span>
}
// maskAuthorization replaces any "Authorization: &lt;token&gt;" header with "Authorization: *", hiding sensitive credentials.
func maskAuthorization(headers []string) <span class="cov8" title="1">{
for idx, header := range headers </span><span class="cov8" title="1">{
key, _, _ := strings.Cut(header, ":")
if strings.EqualFold(key, "Authorization") </span><span class="cov8" title="1">{
headers[idx] = key + ": *"
}</span>
}
}
// source returns a space-trimmed slice of the n'th line.
func source(lines [][]byte, n int) []byte <span class="cov8" title="1">{
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
if n &lt; 0 || n &gt;= len(lines) </span><span class="cov8" title="1">{
return dunnoBytes
}</span>
<span class="cov8" title="1">return bytes.TrimSpace(lines[n])</span>
}
// function returns, if possible, the name of the function containing the PC.
func function(pc uintptr) string <span class="cov8" title="1">{
fn := runtime.FuncForPC(pc)
if fn == nil </span><span class="cov8" title="1">{
return dunno
}</span>
<span class="cov8" title="1">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 &gt;= 0 </span><span class="cov8" title="1">{
name = name[lastSlash+1:]
}</span>
<span class="cov8" title="1">if period := strings.IndexByte(name, '.'); period &gt;= 0 </span><span class="cov8" title="1">{
name = name[period+1:]
}</span>
<span class="cov8" title="1">name = strings.ReplaceAll(name, "·", ".")
return name</span>
}
// timeFormat returns a customized time string for logger.
func timeFormat(t time.Time) string <span class="cov8" title="1">{
return t.Format("2006/01/02 - 15:04:05")
}</span>
</pre>
<pre class="file" id="file30" style="display: none">// 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) <span class="cov8" title="1">{
r.WriteContentType(w)
_, err = w.Write(r.Data)
return
}</span>
// WriteContentType (Data) writes custom ContentType.
func (r Data) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, []string{r.ContentType})
}</span>
</pre>
<pre class="file" id="file31" style="display: none">// 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 <span class="cov8" title="1">{
return HTML{
Template: r.Template,
Name: name,
Data: data,
}
}</span>
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
func (r HTMLDebug) Instance(name string, data any) Render <span class="cov8" title="1">{
return HTML{
Template: r.loadTemplate(),
Name: name,
Data: data,
}
}</span>
func (r HTMLDebug) loadTemplate() *template.Template <span class="cov8" title="1">{
if r.FuncMap == nil </span><span class="cov8" title="1">{
r.FuncMap = template.FuncMap{}
}</span>
<span class="cov8" title="1">if len(r.Files) &gt; 0 </span><span class="cov8" title="1">{
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
}</span>
<span class="cov8" title="1">if r.Glob != "" </span><span class="cov8" title="1">{
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
}</span>
<span class="cov8" title="1">if r.FileSystem != nil &amp;&amp; len(r.Patterns) &gt; 0 </span><span class="cov8" title="1">{
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(
fs.FileSystem{FileSystem: r.FileSystem}, r.Patterns...))
}</span>
<span class="cov8" title="1">panic("the HTML debug render was created without files or glob pattern or file system with patterns")</span>
}
// Render (HTML) executes template and writes its result with custom ContentType for response.
func (r HTML) Render(w http.ResponseWriter) error <span class="cov8" title="1">{
r.WriteContentType(w)
if r.Name == "" </span><span class="cov8" title="1">{
return r.Template.Execute(w, r.Data)
}</span>
<span class="cov8" title="1">return r.Template.ExecuteTemplate(w, r.Name, r.Data)</span>
}
// WriteContentType (HTML) writes HTML ContentType.
func (r HTML) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, htmlContentType)
}</span>
</pre>
<pre class="file" id="file32" style="display: none">// 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 <span class="cov8" title="1">{
return WriteJSON(w, r.Data)
}</span>
// WriteContentType (JSON) writes JSON ContentType.
func (r JSON) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, jsonContentType)
}</span>
// WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj any) error <span class="cov8" title="1">{
writeContentType(w, jsonContentType)
jsonBytes, err := json.API.Marshal(obj)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">_, err = w.Write(jsonBytes)
return err</span>
}
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
func (r IndentedJSON) Render(w http.ResponseWriter) error <span class="cov8" title="1">{
r.WriteContentType(w)
jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ")
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">_, err = w.Write(jsonBytes)
return err</span>
}
// WriteContentType (IndentedJSON) writes JSON ContentType.
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, jsonContentType)
}</span>
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
func (r SecureJSON) Render(w http.ResponseWriter) error <span class="cov8" title="1">{
r.WriteContentType(w)
jsonBytes, err := json.API.Marshal(r.Data)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
// if the jsonBytes is array values
<span class="cov8" title="1">if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) &amp;&amp; bytes.HasSuffix(jsonBytes,
bytesconv.StringToBytes("]")) </span><span class="cov8" title="1">{
if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil </span><span class="cov8" title="1">{
return err
}</span>
}
<span class="cov8" title="1">_, err = w.Write(jsonBytes)
return err</span>
}
// WriteContentType (SecureJSON) writes JSON ContentType.
func (r SecureJSON) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, jsonContentType)
}</span>
// 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) <span class="cov8" title="1">{
r.WriteContentType(w)
ret, err := json.API.Marshal(r.Data)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if r.Callback == "" </span><span class="cov8" title="1">{
_, err = w.Write(ret)
return err
}</span>
<span class="cov8" title="1">callback := template.JSEscapeString(r.Callback)
if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if _, err = w.Write(ret); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">return nil</span>
}
// WriteContentType (JsonpJSON) writes Javascript ContentType.
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, jsonpContentType)
}</span>
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
func (r AsciiJSON) Render(w http.ResponseWriter) error <span class="cov8" title="1">{
r.WriteContentType(w)
ret, err := json.API.Marshal(r.Data)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">var buffer bytes.Buffer
escapeBuf := make([]byte, 0, 6) // Preallocate 6 bytes for Unicode escape sequences
for _, r := range bytesconv.BytesToString(ret) </span><span class="cov8" title="1">{
if r &gt; unicode.MaxASCII </span><span class="cov8" title="1">{
escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) // Reuse escapeBuf
buffer.Write(escapeBuf)
}</span> else<span class="cov8" title="1"> {
buffer.WriteByte(byte(r))
}</span>
}
<span class="cov8" title="1">_, err = w.Write(buffer.Bytes())
return err</span>
}
// WriteContentType (AsciiJSON) writes JSON ContentType.
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, jsonASCIIContentType)
}</span>
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
func (r PureJSON) Render(w http.ResponseWriter) error <span class="cov8" title="1">{
r.WriteContentType(w)
encoder := json.API.NewEncoder(w)
encoder.SetEscapeHTML(false)
return encoder.Encode(r.Data)
}</span>
// WriteContentType (PureJSON) writes custom ContentType.
func (r PureJSON) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, jsonContentType)
}</span>
</pre>
<pre class="file" id="file33" style="display: none">// 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) <span class="cov8" title="1">{
writeContentType(w, msgpackContentType)
}</span>
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
func (r MsgPack) Render(w http.ResponseWriter) error <span class="cov8" title="1">{
return WriteMsgPack(w, r.Data)
}</span>
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
func WriteMsgPack(w http.ResponseWriter, obj any) error <span class="cov8" title="1">{
writeContentType(w, msgpackContentType)
var mh codec.MsgpackHandle
return codec.NewEncoder(w, &amp;mh).Encode(obj)
}</span>
</pre>
<pre class="file" id="file34" style="display: none">// 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 <span class="cov8" title="1">{
r.WriteContentType(w)
bytes, err := proto.Marshal(r.Data.(proto.Message))
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">_, err = w.Write(bytes)
return err</span>
}
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, protobufContentType)
}</span>
</pre>
<pre class="file" id="file35" style="display: none">// 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) <span class="cov8" title="1">{
r.WriteContentType(w)
if r.ContentLength &gt;= 0 </span><span class="cov8" title="1">{
if r.Headers == nil </span><span class="cov8" title="1">{
r.Headers = map[string]string{}
}</span>
<span class="cov8" title="1">r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)</span>
}
<span class="cov8" title="1">r.writeHeaders(w, r.Headers)
_, err = io.Copy(w, r.Reader)
return</span>
}
// WriteContentType (Reader) writes custom ContentType.
func (r Reader) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, []string{r.ContentType})
}</span>
// writeHeaders writes custom Header.
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) <span class="cov8" title="1">{
header := w.Header()
for k, v := range headers </span><span class="cov8" title="1">{
if header.Get(k) == "" </span><span class="cov8" title="1">{
header.Set(k, v)
}</span>
}
}
</pre>
<pre class="file" id="file36" style="display: none">// 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 <span class="cov8" title="1">{
if (r.Code &lt; http.StatusMultipleChoices || r.Code &gt; http.StatusPermanentRedirect) &amp;&amp; r.Code != http.StatusCreated </span><span class="cov8" title="1">{
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))</span>
}
<span class="cov8" title="1">http.Redirect(w, r.Request, r.Location, r.Code)
return nil</span>
}
// WriteContentType (Redirect) don't write any ContentType.
func (r Redirect) WriteContentType(http.ResponseWriter) {<span class="cov8" title="1">}</span>
</pre>
<pre class="file" id="file37" style="display: none">// 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) <span class="cov8" title="1">{
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 </span><span class="cov8" title="1">{
header["Content-Type"] = value
}</span>
}
</pre>
<pre class="file" id="file38" style="display: none">// 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 <span class="cov8" title="1">{
return WriteString(w, r.Format, r.Data)
}</span>
// WriteContentType (String) writes Plain ContentType.
func (r String) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, plainContentType)
}</span>
// WriteString writes data according to its format and write custom ContentType.
func WriteString(w http.ResponseWriter, format string, data []any) (err error) <span class="cov8" title="1">{
writeContentType(w, plainContentType)
if len(data) &gt; 0 </span><span class="cov8" title="1">{
_, err = fmt.Fprintf(w, format, data...)
return
}</span>
<span class="cov8" title="1">_, err = w.Write(bytesconv.StringToBytes(format))
return</span>
}
</pre>
<pre class="file" id="file39" style="display: none">// 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 <span class="cov8" title="1">{
r.WriteContentType(w)
bytes, err := toml.Marshal(r.Data)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">_, err = w.Write(bytes)
return err</span>
}
// WriteContentType (TOML) writes TOML ContentType for response.
func (r TOML) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, TOMLContentType)
}</span>
</pre>
<pre class="file" id="file40" style="display: none">// 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 <span class="cov8" title="1">{
r.WriteContentType(w)
return xml.NewEncoder(w).Encode(r.Data)
}</span>
// WriteContentType (XML) writes XML ContentType for response.
func (r XML) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, xmlContentType)
}</span>
</pre>
<pre class="file" id="file41" style="display: none">// 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 <span class="cov8" title="1">{
r.WriteContentType(w)
bytes, err := yaml.Marshal(r.Data)
if err != nil </span><span class="cov8" title="1">{
return err
}</span>
<span class="cov8" title="1">_, err = w.Write(bytes)
return err</span>
}
// WriteContentType (YAML) writes YAML ContentType for response.
func (r YAML) WriteContentType(w http.ResponseWriter) <span class="cov8" title="1">{
writeContentType(w, yamlContentType)
}</span>
</pre>
<pre class="file" id="file42" style="display: none">// 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 <span class="cov8" title="1">{
return w.ResponseWriter
}</span>
func (w *responseWriter) reset(writer http.ResponseWriter) <span class="cov8" title="1">{
w.ResponseWriter = writer
w.size = noWritten
w.status = defaultStatus
}</span>
func (w *responseWriter) WriteHeader(code int) <span class="cov8" title="1">{
if code &gt; 0 &amp;&amp; w.status != code </span><span class="cov8" title="1">{
if w.Written() </span><span class="cov8" title="1">{
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
return
}</span>
<span class="cov8" title="1">w.status = code</span>
}
}
func (w *responseWriter) WriteHeaderNow() <span class="cov8" title="1">{
if !w.Written() </span><span class="cov8" title="1">{
w.size = 0
w.ResponseWriter.WriteHeader(w.status)
}</span>
}
func (w *responseWriter) Write(data []byte) (n int, err error) <span class="cov8" title="1">{
w.WriteHeaderNow()
n, err = w.ResponseWriter.Write(data)
w.size += n
return
}</span>
func (w *responseWriter) WriteString(s string) (n int, err error) <span class="cov8" title="1">{
w.WriteHeaderNow()
n, err = io.WriteString(w.ResponseWriter, s)
w.size += n
return
}</span>
func (w *responseWriter) Status() int <span class="cov8" title="1">{
return w.status
}</span>
func (w *responseWriter) Size() int <span class="cov8" title="1">{
return w.size
}</span>
func (w *responseWriter) Written() bool <span class="cov8" title="1">{
return w.size != noWritten
}</span>
// Hijack implements the http.Hijacker interface.
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) <span class="cov8" title="1">{
if w.size &lt; 0 </span><span class="cov8" title="1">{
w.size = 0
}</span>
<span class="cov8" title="1">return w.ResponseWriter.(http.Hijacker).Hijack()</span>
}
// CloseNotify implements the http.CloseNotifier interface.
func (w *responseWriter) CloseNotify() &lt;-chan bool <span class="cov8" title="1">{
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
}</span>
// Flush implements the http.Flusher interface.
func (w *responseWriter) Flush() <span class="cov8" title="1">{
w.WriteHeaderNow()
w.ResponseWriter.(http.Flusher).Flush()
}</span>
func (w *responseWriter) Pusher() (pusher http.Pusher) <span class="cov8" title="1">{
if pusher, ok := w.ResponseWriter.(http.Pusher); ok </span><span class="cov8" title="1">{
return pusher
}</span>
<span class="cov8" title="1">return nil</span>
}
</pre>
<pre class="file" id="file43" style="display: none">// 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 <span class="cov8" title="1">{
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}</span>
// 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 <span class="cov8" title="1">{
return &amp;RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}</span>
// 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 <span class="cov8" title="1">{
return group.basePath
}</span>
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes <span class="cov8" title="1">{
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}</span>
// 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 <span class="cov8" title="1">{
if matched := regEnLetter.MatchString(httpMethod); !matched </span><span class="cov8" title="1">{
panic("http method " + httpMethod + " is not valid")</span>
}
<span class="cov8" title="1">return group.handle(httpMethod, relativePath, handlers)</span>
}
// POST is a shortcut for router.Handle("POST", path, handlers).
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodPost, relativePath, handlers)
}</span>
// GET is a shortcut for router.Handle("GET", path, handlers).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodGet, relativePath, handlers)
}</span>
// DELETE is a shortcut for router.Handle("DELETE", path, handlers).
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodDelete, relativePath, handlers)
}</span>
// PATCH is a shortcut for router.Handle("PATCH", path, handlers).
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodPatch, relativePath, handlers)
}</span>
// PUT is a shortcut for router.Handle("PUT", path, handlers).
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodPut, relativePath, handlers)
}</span>
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handlers).
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodOptions, relativePath, handlers)
}</span>
// HEAD is a shortcut for router.Handle("HEAD", path, handlers).
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
return group.handle(http.MethodHead, relativePath, handlers)
}</span>
// 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 <span class="cov8" title="1">{
for _, method := range anyMethods </span><span class="cov8" title="1">{
group.handle(method, relativePath, handlers)
}</span>
<span class="cov8" title="1">return group.returnObj()</span>
}
// Match registers a route that matches the specified methods that you declared.
func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes <span class="cov8" title="1">{
for _, method := range methods </span><span class="cov8" title="1">{
group.handle(method, relativePath, handlers)
}</span>
<span class="cov8" title="1">return group.returnObj()</span>
}
// 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 <span class="cov8" title="1">{
return group.staticFileHandler(relativePath, func(c *Context) </span><span class="cov8" title="1">{
c.File(filepath)
}</span>)
}
// 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 <span class="cov8" title="1">{
return group.staticFileHandler(relativePath, func(c *Context) </span><span class="cov8" title="1">{
c.FileFromFS(filepath, fs)
}</span>)
}
func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes <span class="cov8" title="1">{
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") </span><span class="cov8" title="1">{
panic("URL parameters can not be used when serving a static file")</span>
}
<span class="cov8" title="1">group.GET(relativePath, handler)
group.HEAD(relativePath, handler)
return group.returnObj()</span>
}
// 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 <span class="cov8" title="1">{
return group.StaticFS(relativePath, Dir(root, false))
}</span>
// 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 <span class="cov8" title="1">{
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") </span><span class="cov8" title="1">{
panic("URL parameters can not be used when serving a static folder")</span>
}
<span class="cov8" title="1">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()</span>
}
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc <span class="cov8" title="1">{
absolutePath := group.calculateAbsolutePath(relativePath)
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
return func(c *Context) </span><span class="cov8" title="1">{
if _, noListing := fs.(*OnlyFilesFS); noListing </span><span class="cov8" title="1">{
c.Writer.WriteHeader(http.StatusNotFound)
}</span>
<span class="cov8" title="1">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 </span><span class="cov8" title="1">{
c.Writer.WriteHeader(http.StatusNotFound)
c.handlers = group.engine.noRoute
// Reset index
c.index = -1
return
}</span>
<span class="cov8" title="1">f.Close()
fileServer.ServeHTTP(c.Writer, c.Request)</span>
}
}
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain <span class="cov8" title="1">{
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize &lt; int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}</span>
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string <span class="cov8" title="1">{
return joinPaths(group.basePath, relativePath)
}</span>
func (group *RouterGroup) returnObj() IRoutes <span class="cov8" title="1">{
if group.root </span><span class="cov8" title="1">{
return group.engine
}</span>
<span class="cov8" title="1">return group</span>
}
</pre>
<pre class="file" id="file44" style="display: none">// 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) <span class="cov8" title="1">{
r = New()
c = r.allocateContext(0)
c.reset()
c.writermem.reset(w)
return
}</span>
// CreateTestContextOnly returns a fresh context base on the engine for testing purposes
func CreateTestContextOnly(w http.ResponseWriter, r *Engine) (c *Context) <span class="cov8" title="1">{
c = r.allocateContext(r.maxParams)
c.reset()
c.writermem.reset(w)
return
}</span>
</pre>
<pre class="file" id="file45" style="display: none">// 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) <span class="cov8" title="1">{
for _, entry := range ps </span><span class="cov8" title="1">{
if entry.Key == name </span><span class="cov8" title="1">{
return entry.Value, true
}</span>
}
<span class="cov8" title="1">return "", false</span>
}
// 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) <span class="cov8" title="1">{
va, _ = ps.Get(name)
return
}</span>
type methodTree struct {
method string
root *node
}
type methodTrees []methodTree
func (trees methodTrees) get(method string) *node <span class="cov8" title="1">{
for _, tree := range trees </span><span class="cov8" title="1">{
if tree.method == method </span><span class="cov8" title="1">{
return tree.root
}</span>
}
<span class="cov8" title="1">return nil</span>
}
func longestCommonPrefix(a, b string) int <span class="cov8" title="1">{
i := 0
max_ := min(len(a), len(b))
for i &lt; max_ &amp;&amp; a[i] == b[i] </span><span class="cov8" title="1">{
i++
}</span>
<span class="cov8" title="1">return i</span>
}
// addChild will add a child node, keeping wildcardChild at the end
func (n *node) addChild(child *node) <span class="cov8" title="1">{
if n.wildChild &amp;&amp; len(n.children) &gt; 0 </span><span class="cov8" title="1">{
wildcardChild := n.children[len(n.children)-1]
n.children = append(n.children[:len(n.children)-1], child, wildcardChild)
}</span> else<span class="cov8" title="1"> {
n.children = append(n.children, child)
}</span>
}
func countParams(path string) uint16 <span class="cov8" title="1">{
var n uint16
s := bytesconv.StringToBytes(path)
n += uint16(bytes.Count(s, strColon))
n += uint16(bytes.Count(s, strStar))
return n
}</span>
func countSections(path string) uint16 <span class="cov8" title="1">{
s := bytesconv.StringToBytes(path)
return uint16(bytes.Count(s, strSlash))
}</span>
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 <span class="cov8" title="1">{
cs := n.children
cs[pos].priority++
prio := cs[pos].priority
// Adjust position (move to front)
newPos := pos
for ; newPos &gt; 0 &amp;&amp; cs[newPos-1].priority &lt; prio; newPos-- </span><span class="cov8" title="1">{
// Swap node positions
cs[newPos-1], cs[newPos] = cs[newPos], cs[newPos-1]
}</span>
// Build new index char string
<span class="cov8" title="1">if newPos != pos </span><span class="cov8" title="1">{
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'
}</span>
<span class="cov8" title="1">return newPos</span>
}
// addRoute adds a node with the given handle to the path.
// Not concurrency-safe!
func (n *node) addRoute(path string, handlers HandlersChain) <span class="cov8" title="1">{
fullPath := path
n.priority++
// Empty tree
if len(n.path) == 0 &amp;&amp; len(n.children) == 0 </span><span class="cov8" title="1">{
n.insertChild(path, fullPath, handlers)
n.nType = root
return
}</span>
<span class="cov8" title="1">parentFullPathIndex := 0
walk:
for </span><span class="cov8" title="1">{
// 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 &lt; len(n.path) </span><span class="cov8" title="1">{
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{&amp;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]
}</span>
// Make new node a child of this node
<span class="cov8" title="1">if i &lt; len(path) </span><span class="cov8" title="1">{
path = path[i:]
c := path[0]
// '/' after param
if n.nType == param &amp;&amp; c == '/' &amp;&amp; len(n.children) == 1 </span><span class="cov8" title="1">{
parentFullPathIndex += len(n.path)
n = n.children[0]
n.priority++
continue walk</span>
}
// Check if a child with the next path byte exists
<span class="cov8" title="1">for i, max_ := 0, len(n.indices); i &lt; max_; i++ </span><span class="cov8" title="1">{
if c == n.indices[i] </span><span class="cov8" title="1">{
parentFullPathIndex += len(n.path)
i = n.incrementChildPrio(i)
n = n.children[i]
continue walk</span>
}
}
// Otherwise insert it
<span class="cov8" title="1">if c != ':' &amp;&amp; c != '*' &amp;&amp; n.nType != catchAll </span><span class="cov8" title="1">{
// []byte for proper unicode char conversion, see #65
n.indices += bytesconv.BytesToString([]byte{c})
child := &amp;node{
fullPath: fullPath,
}
n.addChild(child)
n.incrementChildPrio(len(n.indices) - 1)
n = child
}</span> else<span class="cov8" title="1"> if n.wildChild </span><span class="cov8" title="1">{
// 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) &gt;= len(n.path) &amp;&amp; n.path == path[:len(n.path)] &amp;&amp;
// Adding a child to a catchAll is not possible
n.nType != catchAll &amp;&amp;
// Check for longer wildcard, e.g. :name and :names
(len(n.path) &gt;= len(path) || path[len(n.path)] == '/') </span><span class="cov8" title="1">{
continue walk</span>
}
// Wildcard conflict
<span class="cov8" title="1">pathSeg := path
if n.nType != catchAll </span><span class="cov8" title="1">{
pathSeg, _, _ = strings.Cut(pathSeg, "/")
}</span>
<span class="cov8" title="1">prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path +
"' in existing prefix '" + prefix +
"'")</span>
}
<span class="cov8" title="1">n.insertChild(path, fullPath, handlers)
return</span>
}
// Otherwise add handle to current node
<span class="cov8" title="1">if n.handlers != nil </span><span class="cov8" title="1">{
panic("handlers are already registered for path '" + fullPath + "'")</span>
}
<span class="cov8" title="1">n.handlers = handlers
n.fullPath = fullPath
return</span>
}
}
// 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) <span class="cov8" title="1">{
// Find start
escapeColon := false
for start, c := range []byte(path) </span><span class="cov8" title="1">{
if escapeColon </span><span class="cov8" title="1">{
escapeColon = false
if c == ':' </span><span class="cov8" title="1">{
continue</span>
}
<span class="cov8" title="1">panic("invalid escape string in path '" + path + "'")</span>
}
<span class="cov8" title="1">if c == '\\' </span><span class="cov8" title="1">{
escapeColon = true
continue</span>
}
// A wildcard starts with ':' (param) or '*' (catch-all)
<span class="cov8" title="1">if c != ':' &amp;&amp; c != '*' </span><span class="cov8" title="1">{
continue</span>
}
// Find end and check for invalid characters
<span class="cov8" title="1">valid = true
for end, c := range []byte(path[start+1:]) </span><span class="cov8" title="1">{
switch c </span>{
case '/':<span class="cov8" title="1">
return path[start : start+1+end], start, valid</span>
case ':', '*':<span class="cov8" title="1">
valid = false</span>
}
}
<span class="cov8" title="1">return path[start:], start, valid</span>
}
<span class="cov8" title="1">return "", -1, false</span>
}
func (n *node) insertChild(path string, fullPath string, handlers HandlersChain) <span class="cov8" title="1">{
for </span><span class="cov8" title="1">{
// Find prefix until first wildcard
wildcard, i, valid := findWildcard(path)
if i &lt; 0 </span><span class="cov8" title="1">{ // No wildcard found
break</span>
}
// The wildcard name must only contain one ':' or '*' character
<span class="cov8" title="1">if !valid </span><span class="cov8" title="1">{
panic("only one wildcard per path segment is allowed, has: '" +
wildcard + "' in path '" + fullPath + "'")</span>
}
// check if the wildcard has a name
<span class="cov8" title="1">if len(wildcard) &lt; 2 </span><span class="cov8" title="1">{
panic("wildcards must be named with a non-empty name in path '" + fullPath + "'")</span>
}
<span class="cov8" title="1">if wildcard[0] == ':' </span><span class="cov8" title="1">{ // param
if i &gt; 0 </span><span class="cov8" title="1">{
// Insert prefix before the current wildcard
n.path = path[:i]
path = path[i:]
}</span>
<span class="cov8" title="1">child := &amp;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) &lt; len(path) </span><span class="cov8" title="1">{
path = path[len(wildcard):]
child := &amp;node{
priority: 1,
fullPath: fullPath,
}
n.addChild(child)
n = child
continue</span>
}
// Otherwise we're done. Insert the handle in the new leaf
<span class="cov8" title="1">n.handlers = handlers
return</span>
}
// catchAll
<span class="cov8" title="1">if i+len(wildcard) != len(path) </span><span class="cov8" title="1">{
panic("catch-all routes are only allowed at the end of the path in path '" + fullPath + "'")</span>
}
<span class="cov8" title="1">if len(n.path) &gt; 0 &amp;&amp; n.path[len(n.path)-1] == '/' </span><span class="cov8" title="1">{
pathSeg := ""
if len(n.children) != 0 </span><span class="cov8" title="1">{
pathSeg, _, _ = strings.Cut(n.children[0].path, "/")
}</span>
<span class="cov8" title="1">panic("catch-all wildcard '" + path +
"' in new path '" + fullPath +
"' conflicts with existing path segment '" + pathSeg +
"' in existing prefix '" + n.path + pathSeg +
"'")</span>
}
// currently fixed width 1 for '/'
<span class="cov8" title="1">i--
if i &lt; 0 || path[i] != '/' </span><span class="cov8" title="1">{
panic("no / before catch-all in path '" + fullPath + "'")</span>
}
<span class="cov8" title="1">n.path = path[:i]
// First node: catchAll node with empty path
child := &amp;node{
wildChild: true,
nType: catchAll,
fullPath: fullPath,
}
n.addChild(child)
n.indices = string('/')
n = child
n.priority++
// second node: node holding the variable
child = &amp;node{
path: path[i:],
nType: catchAll,
handlers: handlers,
priority: 1,
fullPath: fullPath,
}
n.children = []*node{child}
return</span>
}
// If no wildcard was found, simply insert the path and handle
<span class="cov8" title="1">n.path = path
n.handlers = handlers
n.fullPath = fullPath</span>
}
// 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) <span class="cov8" title="1">{
var globalParamsCount int16
walk: // Outer loop for walking the tree
for </span><span class="cov8" title="1">{
prefix := n.path
if len(path) &gt; len(prefix) </span><span class="cov8" title="1">{
if path[:len(prefix)] == prefix </span><span class="cov8" title="1">{
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) </span><span class="cov8" title="1">{
if c == idxc </span><span class="cov8" title="1">{
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
if n.wildChild </span><span class="cov8" title="1">{
index := len(*skippedNodes)
*skippedNodes = (*skippedNodes)[:index+1]
(*skippedNodes)[index] = skippedNode{
path: prefix + path,
node: &amp;node{
path: n.path,
wildChild: n.wildChild,
nType: n.nType,
priority: n.priority,
children: n.children,
handlers: n.handlers,
fullPath: n.fullPath,
},
paramsCount: globalParamsCount,
}
}</span>
<span class="cov8" title="1">n = n.children[i]
continue walk</span>
}
}
<span class="cov8" title="1">if !n.wildChild </span><span class="cov8" title="1">{
// 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 != "/" </span><span class="cov8" title="1">{
for length := len(*skippedNodes); length &gt; 0; length-- </span><span class="cov8" title="1">{
skippedNode := (*skippedNodes)[length-1]
*skippedNodes = (*skippedNodes)[:length-1]
if strings.HasSuffix(skippedNode.path, path) </span><span class="cov8" title="1">{
path = skippedNode.path
n = skippedNode.node
if value.params != nil </span><span class="cov8" title="1">{
*value.params = (*value.params)[:skippedNode.paramsCount]
}</span>
<span class="cov8" title="1">globalParamsCount = skippedNode.paramsCount
continue walk</span>
}
}
}
// Nothing found.
// We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path.
<span class="cov8" title="1">value.tsr = path == "/" &amp;&amp; n.handlers != nil
return value</span>
}
// Handle wildcard child, which is always at the end of the array
<span class="cov8" title="1">n = n.children[len(n.children)-1]
globalParamsCount++
switch n.nType </span>{
case param:<span class="cov8" title="1">
// fix truncate the parameter
// tree_test.go line: 204
// Find param end (either '/' or path end)
end := 0
for end &lt; len(path) &amp;&amp; path[end] != '/' </span><span class="cov8" title="1">{
end++
}</span>
// Save param value
<span class="cov8" title="1">if params != nil </span><span class="cov8" title="1">{
// Preallocate capacity if necessary
if cap(*params) &lt; int(globalParamsCount) </span><span class="cov8" title="1">{
newParams := make(Params, len(*params), globalParamsCount)
copy(newParams, *params)
*params = newParams
}</span>
<span class="cov8" title="1">if value.params == nil </span><span class="cov8" title="1">{
value.params = params
}</span>
// Expand slice within preallocated capacity
<span class="cov8" title="1">i := len(*value.params)
*value.params = (*value.params)[:i+1]
val := path[:end]
if unescape </span><span class="cov8" title="1">{
if v, err := url.QueryUnescape(val); err == nil </span><span class="cov8" title="1">{
val = v
}</span>
}
<span class="cov8" title="1">(*value.params)[i] = Param{
Key: n.path[1:],
Value: val,
}</span>
}
// we need to go deeper!
<span class="cov8" title="1">if end &lt; len(path) </span><span class="cov8" title="1">{
if len(n.children) &gt; 0 </span><span class="cov8" title="1">{
path = path[end:]
n = n.children[0]
continue walk</span>
}
// ... but we can't
<span class="cov8" title="1">value.tsr = len(path) == end+1
return value</span>
}
<span class="cov8" title="1">if value.handlers = n.handlers; value.handlers != nil </span><span class="cov8" title="1">{
value.fullPath = n.fullPath
return value
}</span>
<span class="cov8" title="1">if len(n.children) == 1 </span><span class="cov8" title="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 == "/" &amp;&amp; n.handlers != nil) || (n.path == "" &amp;&amp; n.indices == "/")
}</span>
<span class="cov8" title="1">return value</span>
case catchAll:<span class="cov8" title="1">
// Save param value
if params != nil </span><span class="cov8" title="1">{
// Preallocate capacity if necessary
if cap(*params) &lt; int(globalParamsCount) </span><span class="cov8" title="1">{
newParams := make(Params, len(*params), globalParamsCount)
copy(newParams, *params)
*params = newParams
}</span>
<span class="cov8" title="1">if value.params == nil </span><span class="cov8" title="1">{
value.params = params
}</span>
// Expand slice within preallocated capacity
<span class="cov8" title="1">i := len(*value.params)
*value.params = (*value.params)[:i+1]
val := path
if unescape </span><span class="cov8" title="1">{
if v, err := url.QueryUnescape(path); err == nil </span><span class="cov8" title="1">{
val = v
}</span>
}
<span class="cov8" title="1">(*value.params)[i] = Param{
Key: n.path[2:],
Value: val,
}</span>
}
<span class="cov8" title="1">value.handlers = n.handlers
value.fullPath = n.fullPath
return value</span>
default:<span class="cov8" title="1">
panic("invalid node type")</span>
}
}
}
<span class="cov8" title="1">if path == prefix </span><span class="cov8" title="1">{
// 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 &amp;&amp; path != "/" </span><span class="cov8" title="1">{
for length := len(*skippedNodes); length &gt; 0; length-- </span><span class="cov8" title="1">{
skippedNode := (*skippedNodes)[length-1]
*skippedNodes = (*skippedNodes)[:length-1]
if strings.HasSuffix(skippedNode.path, path) </span><span class="cov8" title="1">{
path = skippedNode.path
n = skippedNode.node
if value.params != nil </span><span class="cov8" title="1">{
*value.params = (*value.params)[:skippedNode.paramsCount]
}</span>
<span class="cov8" title="1">globalParamsCount = skippedNode.paramsCount
continue walk</span>
}
}
// n = latestNode.children[len(latestNode.children)-1]
}
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
<span class="cov8" title="1">if value.handlers = n.handlers; value.handlers != nil </span><span class="cov8" title="1">{
value.fullPath = n.fullPath
return value
}</span>
// 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
<span class="cov8" title="1">if path == "/" &amp;&amp; n.wildChild &amp;&amp; n.nType != root </span><span class="cov8" title="1">{
value.tsr = true
return value
}</span>
<span class="cov8" title="1">if path == "/" &amp;&amp; n.nType == static </span><span class="cov8" title="1">{
value.tsr = true
return value
}</span>
// No handle found. Check if a handle for this path + a
// trailing slash exists for trailing slash recommendation
<span class="cov8" title="1">for i, c := range []byte(n.indices) </span><span class="cov8" title="1">{
if c == '/' </span><span class="cov8" title="1">{
n = n.children[i]
value.tsr = (len(n.path) == 1 &amp;&amp; n.handlers != nil) ||
(n.nType == catchAll &amp;&amp; n.children[0].handlers != nil)
return value
}</span>
}
<span class="cov8" title="1">return value</span>
}
// Nothing found. We can recommend to redirect to the same URL with an
// extra trailing slash if a leaf exists for that path
<span class="cov8" title="1">value.tsr = path == "/" ||
(len(prefix) == len(path)+1 &amp;&amp; prefix[len(path)] == '/' &amp;&amp;
path == prefix[:len(prefix)-1] &amp;&amp; n.handlers != nil)
// roll back to last valid skippedNode
if !value.tsr &amp;&amp; path != "/" </span><span class="cov8" title="1">{
for length := len(*skippedNodes); length &gt; 0; length-- </span><span class="cov8" title="1">{
skippedNode := (*skippedNodes)[length-1]
*skippedNodes = (*skippedNodes)[:length-1]
if strings.HasSuffix(skippedNode.path, path) </span><span class="cov8" title="1">{
path = skippedNode.path
n = skippedNode.node
if value.params != nil </span><span class="cov8" title="1">{
*value.params = (*value.params)[:skippedNode.paramsCount]
}</span>
<span class="cov8" title="1">globalParamsCount = skippedNode.paramsCount
continue walk</span>
}
}
}
<span class="cov8" title="1">return value</span>
}
}
// 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) <span class="cov8" title="1">{
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 &gt; stackBufSize </span><span class="cov8" title="1">{
buf = make([]byte, 0, length)
}</span>
<span class="cov8" title="1">ciPath := n.findCaseInsensitivePathRec(
path,
buf, // Preallocate enough memory for new path
[4]byte{}, // Empty rune buffer
fixTrailingSlash,
)
return ciPath, ciPath != nil</span>
}
// Shift bytes in array by n bytes left
func shiftNRuneBytes(rb [4]byte, n int) [4]byte <span class="cov8" title="1">{
switch n </span>{
case 0:<span class="cov8" title="1">
return rb</span>
case 1:<span class="cov8" title="1">
return [4]byte{rb[1], rb[2], rb[3], 0}</span>
case 2:<span class="cov8" title="1">
return [4]byte{rb[2], rb[3]}</span>
case 3:<span class="cov8" title="1">
return [4]byte{rb[3]}</span>
default:<span class="cov8" title="1">
return [4]byte{}</span>
}
}
// Recursive case-insensitive lookup function used by n.findCaseInsensitivePath
func (n *node) findCaseInsensitivePathRec(path string, ciPath []byte, rb [4]byte, fixTrailingSlash bool) []byte <span class="cov8" title="1">{
npLen := len(n.path)
walk: // Outer loop for walking the tree
for len(path) &gt;= npLen &amp;&amp; (npLen == 0 || strings.EqualFold(path[1:npLen], n.path[1:])) </span><span class="cov8" title="1">{
// Add common prefix to result
oldPath := path
path = path[npLen:]
ciPath = append(ciPath, n.path...)
if len(path) == 0 </span><span class="cov8" title="1">{
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
if n.handlers != nil </span><span class="cov8" title="1">{
return ciPath
}</span>
// No handle found.
// Try to fix the path by adding a trailing slash
<span class="cov8" title="1">if fixTrailingSlash </span><span class="cov8" title="1">{
for i, c := range []byte(n.indices) </span><span class="cov8" title="1">{
if c == '/' </span><span class="cov8" title="1">{
n = n.children[i]
if (len(n.path) == 1 &amp;&amp; n.handlers != nil) ||
(n.nType == catchAll &amp;&amp; n.children[0].handlers != nil) </span><span class="cov8" title="1">{
return append(ciPath, '/')
}</span>
<span class="cov8" title="1">return nil</span>
}
}
}
<span class="cov8" title="1">return nil</span>
}
// 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
<span class="cov8" title="1">if !n.wildChild </span><span class="cov8" title="1">{
// Skip rune bytes already processed
rb = shiftNRuneBytes(rb, npLen)
if rb[0] != 0 </span><span class="cov8" title="1">{
// Old rune not finished
idxc := rb[0]
for i, c := range []byte(n.indices) </span><span class="cov8" title="1">{
if c == idxc </span><span class="cov8" title="1">{
// continue with child node
n = n.children[i]
npLen = len(n.path)
continue walk</span>
}
}
} else<span class="cov8" title="1"> {
// 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 &lt; max_; off++ </span><span class="cov8" title="1">{
if i := npLen - off; utf8.RuneStart(oldPath[i]) </span><span class="cov8" title="1">{
// read rune from cached path
rv, _ = utf8.DecodeRuneInString(oldPath[i:])
break</span>
}
}
// Calculate lowercase bytes of current rune
<span class="cov8" title="1">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) </span><span class="cov8" title="1">{
// Lowercase matches
if c == idxc </span><span class="cov8" title="1">{
// 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 </span><span class="cov8" title="1">{
return out
}</span>
<span class="cov8" title="1">break</span>
}
}
// If we found no match, the same for the uppercase rune,
// if it differs
<span class="cov8" title="1">if up := unicode.ToUpper(rv); up != lo </span><span class="cov8" title="1">{
utf8.EncodeRune(rb[:], up)
rb = shiftNRuneBytes(rb, off)
idxc := rb[0]
for i, c := range []byte(n.indices) </span><span class="cov8" title="1">{
// Uppercase matches
if c == idxc </span><span class="cov8" title="1">{
// Continue with child node
n = n.children[i]
npLen = len(n.path)
continue walk</span>
}
}
}
}
// Nothing found. We can recommend to redirect to the same URL
// without a trailing slash if a leaf exists for that path
<span class="cov8" title="1">if fixTrailingSlash &amp;&amp; path == "/" &amp;&amp; n.handlers != nil </span><span class="cov8" title="1">{
return ciPath
}</span>
<span class="cov8" title="1">return nil</span>
}
<span class="cov8" title="1">n = n.children[0]
switch n.nType </span>{
case param:<span class="cov8" title="1">
// Find param end (either '/' or path end)
end := 0
for end &lt; len(path) &amp;&amp; path[end] != '/' </span><span class="cov8" title="1">{
end++
}</span>
// Add param value to case insensitive path
<span class="cov8" title="1">ciPath = append(ciPath, path[:end]...)
// We need to go deeper!
if end &lt; len(path) </span><span class="cov8" title="1">{
if len(n.children) &gt; 0 </span><span class="cov8" title="1">{
// Continue with child node
n = n.children[0]
npLen = len(n.path)
path = path[end:]
continue</span>
}
// ... but we can't
<span class="cov8" title="1">if fixTrailingSlash &amp;&amp; len(path) == end+1 </span><span class="cov8" title="1">{
return ciPath
}</span>
<span class="cov8" title="1">return nil</span>
}
<span class="cov8" title="1">if n.handlers != nil </span><span class="cov8" title="1">{
return ciPath
}</span>
<span class="cov8" title="1">if fixTrailingSlash &amp;&amp; len(n.children) == 1 </span><span class="cov8" title="1">{
// No handle found. Check if a handle for this path + a
// trailing slash exists
n = n.children[0]
if n.path == "/" &amp;&amp; n.handlers != nil </span><span class="cov8" title="1">{
return append(ciPath, '/')
}</span>
}
<span class="cov8" title="1">return nil</span>
case catchAll:<span class="cov8" title="1">
return append(ciPath, path...)</span>
default:<span class="cov8" title="1">
panic("invalid node type")</span>
}
}
// Nothing found.
// Try to fix the path by adding / removing a trailing slash
<span class="cov8" title="1">if fixTrailingSlash </span><span class="cov8" title="1">{
if path == "/" </span><span class="cov8" title="1">{
return ciPath
}</span>
<span class="cov8" title="1">if len(path)+1 == npLen &amp;&amp; n.path[len(path)] == '/' &amp;&amp;
strings.EqualFold(path[1:], n.path[1:len(path)]) &amp;&amp; n.handlers != nil </span><span class="cov8" title="1">{
return append(ciPath, n.path...)
}</span>
}
<span class="cov8" title="1">return nil</span>
}
</pre>
<pre class="file" id="file46" style="display: none">// 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 <span class="cov8" title="1">{
value := reflect.ValueOf(val)
if value.Kind() == reflect.Ptr </span><span class="cov8" title="1">{
panic(`Bind struct can not be a pointer. Example:
Use: gin.Bind(Struct{}) instead of gin.Bind(&amp;Struct{})
`)</span>
}
<span class="cov8" title="1">typ := value.Type()
return func(c *Context) </span><span class="cov8" title="1">{
obj := reflect.New(typ).Interface()
if c.Bind(obj) == nil </span><span class="cov8" title="1">{
c.Set(BindKey, obj)
}</span>
}
}
// WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.
func WrapF(f http.HandlerFunc) HandlerFunc <span class="cov8" title="1">{
return func(c *Context) </span><span class="cov8" title="1">{
f(c.Writer, c.Request)
}</span>
}
// WrapH is a helper function for wrapping http.Handler and returns a Gin middleware.
func WrapH(h http.Handler) HandlerFunc <span class="cov8" title="1">{
return func(c *Context) </span><span class="cov8" title="1">{
h.ServeHTTP(c.Writer, c.Request)
}</span>
}
// 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 <span class="cov8" title="1">{
start.Name = xml.Name{
Space: "",
Local: "map",
}
if err := e.EncodeToken(start); err != nil </span><span class="cov0" title="0">{
return err
}</span>
<span class="cov8" title="1">for key, value := range h </span><span class="cov8" title="1">{
elem := xml.StartElement{
Name: xml.Name{Space: "", Local: key},
Attr: []xml.Attr{},
}
if err := e.EncodeElement(value, elem); err != nil </span><span class="cov8" title="1">{
return err
}</span>
}
<span class="cov8" title="1">return e.EncodeToken(xml.EndElement{Name: start.Name})</span>
}
func assert1(guard bool, text string) <span class="cov8" title="1">{
if !guard </span><span class="cov8" title="1">{
panic(text)</span>
}
}
func filterFlags(content string) string <span class="cov8" title="1">{
for i, char := range content </span><span class="cov8" title="1">{
if char == ' ' || char == ';' </span><span class="cov8" title="1">{
return content[:i]
}</span>
}
<span class="cov8" title="1">return content</span>
}
func chooseData(custom, wildcard any) any <span class="cov8" title="1">{
if custom != nil </span><span class="cov8" title="1">{
return custom
}</span>
<span class="cov8" title="1">if wildcard != nil </span><span class="cov8" title="1">{
return wildcard
}</span>
<span class="cov8" title="1">panic("negotiation config is invalid")</span>
}
func parseAccept(acceptHeader string) []string <span class="cov8" title="1">{
parts := strings.Split(acceptHeader, ",")
out := make([]string, 0, len(parts))
for _, part := range parts </span><span class="cov8" title="1">{
if i := strings.IndexByte(part, ';'); i &gt; 0 </span><span class="cov8" title="1">{
part = part[:i]
}</span>
<span class="cov8" title="1">if part = strings.TrimSpace(part); part != "" </span><span class="cov8" title="1">{
out = append(out, part)
}</span>
}
<span class="cov8" title="1">return out</span>
}
func lastChar(str string) uint8 <span class="cov8" title="1">{
if str == "" </span><span class="cov8" title="1">{
panic("The length of the string can't be 0")</span>
}
<span class="cov8" title="1">return str[len(str)-1]</span>
}
func nameOfFunction(f any) string <span class="cov8" title="1">{
return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
}</span>
func joinPaths(absolutePath, relativePath string) string <span class="cov8" title="1">{
if relativePath == "" </span><span class="cov8" title="1">{
return absolutePath
}</span>
<span class="cov8" title="1">finalPath := path.Join(absolutePath, relativePath)
if lastChar(relativePath) == '/' &amp;&amp; lastChar(finalPath) != '/' </span><span class="cov8" title="1">{
return finalPath + "/"
}</span>
<span class="cov8" title="1">return finalPath</span>
}
func resolveAddress(addr []string) string <span class="cov8" title="1">{
switch len(addr) </span>{
case 0:<span class="cov8" title="1">
if port := os.Getenv("PORT"); port != "" </span><span class="cov8" title="1">{
debugPrint("Environment variable PORT=\"%s\"", port)
return ":" + port
}</span>
<span class="cov8" title="1">debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
return ":8080"</span>
case 1:<span class="cov8" title="1">
return addr[0]</span>
default:<span class="cov8" title="1">
panic("too many parameters")</span>
}
}
// https://stackoverflow.com/questions/53069040/checking-a-string-contains-only-ascii-characters
func isASCII(s string) bool <span class="cov8" title="1">{
for i := 0; i &lt; len(s); i++ </span><span class="cov8" title="1">{
if s[i] &gt; unicode.MaxASCII </span><span class="cov8" title="1">{
return false
}</span>
}
<span class="cov8" title="1">return true</span>
}
</pre>
</div>
</body>
<script>
(function() {
var files = document.getElementById('files');
var visible;
files.addEventListener('change', onChange, false);
function select(part) {
if (visible)
visible.style.display = 'none';
visible = document.getElementById(part);
if (!visible)
return;
files.value = part;
visible.style.display = 'block';
location.hash = part;
}
function onChange() {
select(files.value);
window.scrollTo(0, 0);
}
if (location.hash != "") {
select(location.hash.substr(1));
}
if (!visible) {
select("file0");
}
})();
</script>
</html>