mirror of
https://github.com/gin-gonic/gin.git
synced 2025-10-14 04:08:15 +08:00
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.
7272 lines
304 KiB
HTML
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 > 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 = &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 < len(err); i++ </span><span class="cov8" title="1">{
|
|
if err[i] != nil </span><span class="cov8" title="1">{
|
|
if b.Len() > 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 < 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 << 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 && !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 &&
|
|
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 && 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 < value.NumField(); i++ </span><span class="cov8" title="1">{
|
|
sf := tValue.Field(i)
|
|
if sf.PkgPath != "" && !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) > 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 && !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) > 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(&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 && !go_json && !(sonic && (linux || windows || darwin))
|
|
|
|
package json
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
)
|
|
|
|
// Package indicates what library is being used for JSON encoding.
|
|
const Package = "encoding/json"
|
|
|
|
func init() <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 >> 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 = &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 = &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 &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 < 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 >= 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, &parsedError)
|
|
if !ok </span><span class="cov8" title="1">{
|
|
parsedError = &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 && 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&name=Manu&value=
|
|
// c.Query("id") == "1234"
|
|
// c.Query("name") == "Manu"
|
|
// c.Query("value") == ""
|
|
// c.Query("wtf") == ""
|
|
func (c *Context) Query(key string) (value string) <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&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&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 && 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 --> ("mail@example.com", true) := GetPostForm("email") // set email to "mail@example.com"
|
|
// email= --> ("", true) := GetPostForm("email") // set email to ""
|
|
// --> ("", false) := GetPostForm("email") // do nothing with email
|
|
func (c *Context) GetPostForm(key string) (string, bool) <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 >= 1 && k[0:i] == key </span><span class="cov8" title="1">{
|
|
if j := strings.IndexByte(k[i+1:], ']'); j >= 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) > 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" --> JSON binding
|
|
// "application/xml" --> XML binding
|
|
//
|
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
|
// It decodes the json payload into the struct specified as a pointer.
|
|
// It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.
|
|
func (c *Context) Bind(obj any) error <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, &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" --> JSON binding
|
|
// "application/xml" --> XML binding
|
|
//
|
|
// It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.
|
|
// It decodes the json payload into the struct specified as a pointer.
|
|
// Like c.Bind() but this method does not set the response status code to 400 or abort if input is not valid.
|
|
func (c *Context) ShouldBind(obj any) error <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 && c.engine.ForwardedByClientIP && 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") &&
|
|
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 >= 100 && status <= 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, &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 <-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) > 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 < len(accepted) && i < 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 && c.engine.ContextWithFallback
|
|
hasRequestContext := c.Request != nil && c.Request.Context() != nil
|
|
return hasFallback && 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() <-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(&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 --> %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 && v < 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) // << good place
|
|
|
|
`)
|
|
}</span>
|
|
|
|
func debugPrintError(err error) <span class="cov8" title="1">{
|
|
if err != nil && 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 << 63
|
|
// ErrorTypeRender is used when Context.Render() fails.
|
|
ErrorTypeRender ErrorType = 1 << 62
|
|
// ErrorTypePrivate indicates a private error.
|
|
ErrorTypePrivate ErrorType = 1 << 0
|
|
// ErrorTypePublic indicates a public error.
|
|
ErrorTypePublic ErrorType = 1 << 1
|
|
// ErrorTypeAny indicates any other error.
|
|
ErrorTypeAny ErrorType = 1<<64 - 1
|
|
// ErrorTypeNu indicates any other error.
|
|
ErrorTypeNu = 2
|
|
)
|
|
|
|
// Error represents a error's specification.
|
|
type Error struct {
|
|
Err error
|
|
Type ErrorType
|
|
Meta any
|
|
}
|
|
|
|
type errorMsgs []*Error
|
|
|
|
var _ error = (*Error)(nil)
|
|
|
|
// SetType sets the error's type.
|
|
func (msg *Error) SetType(flags ErrorType) *Error <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 & flags) > 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 > 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(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
|
|
if msg.Meta != nil </span><span class="cov8" title="1">{
|
|
fmt.Fprintf(&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 &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 << 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 > 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 := &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 := &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 &Context{engine: engine, params: &v, skippedNodes: &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) > 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) > 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 > engine.maxParams </span><span class="cov8" title="1">{
|
|
engine.maxParams = paramsCount
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">if sectionsCount := countSections(path); sectionsCount > 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) > 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, &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 >= 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 && len(c.Request.URL.RawPath) > 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 < 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 && rPath != "/" </span><span class="cov8" title="1">{
|
|
if value.tsr && engine.RedirectTrailingSlash </span><span class="cov8" title="1">{
|
|
redirectTrailingSlash(c)
|
|
return
|
|
}</span>
|
|
<span class="cov8" title="1">if engine.RedirectFixedPath && 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 && len(t) > 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) > 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 > 1 && 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 --> %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 >= http.StatusContinue && code < http.StatusOK:<span class="cov8" title="1">
|
|
return white</span>
|
|
case code >= http.StatusOK && code < http.StatusMultipleChoices:<span class="cov8" title="1">
|
|
return green</span>
|
|
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:<span class="cov8" title="1">
|
|
return white</span>
|
|
case code >= http.StatusBadRequest && code < 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 && 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 > 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) > 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()) && !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 > 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 && 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(&ginMode, debugCode)</span>
|
|
case ReleaseMode:<span class="cov8" title="1">
|
|
atomic.StoreInt32(&ginMode, releaseCode)</span>
|
|
case TestMode:<span class="cov8" title="1">
|
|
atomic.StoreInt32(&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 > 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 > 1 && p[n-1] == '/'
|
|
|
|
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
|
// gets completely inlined (bufApp calls).
|
|
// loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
|
|
// calls (except make, if needed).
|
|
|
|
for r < n </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] == '.' && r+1 == n:<span class="cov8" title="1">
|
|
trailing = true
|
|
r++</span>
|
|
|
|
case p[r] == '.' && p[r+1] == '/':<span class="cov8" title="1">
|
|
// . element
|
|
r += 2</span>
|
|
|
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):<span class="cov8" title="1">
|
|
// .. element: remove to last /
|
|
r += 3
|
|
|
|
if w > 1 </span><span class="cov8" title="1">{
|
|
// can backtrack
|
|
w--
|
|
|
|
if len(buf) == 0 </span><span class="cov8" title="1">{
|
|
for w > 1 && p[w] != '/' </span><span class="cov8" title="1">{
|
|
w--
|
|
}</span>
|
|
} else<span class="cov8" title="1"> {
|
|
for w > 1 && 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 > 1 </span><span class="cov8" title="1">{
|
|
bufApp(&buf, p, w, '/')
|
|
w++
|
|
}</span>
|
|
|
|
// Copy element
|
|
<span class="cov8" title="1">for r < n && p[r] != '/' </span><span class="cov8" title="1">{
|
|
bufApp(&buf, p, w, p[r])
|
|
w++
|
|
r++
|
|
}</span>
|
|
}
|
|
}
|
|
|
|
// Re-append trailing slash
|
|
<span class="cov8" title="1">if trailing && w > 1 </span><span class="cov8" title="1">{
|
|
bufApp(&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 > 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) > 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, &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: <token>" 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 < 0 || n >= 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 >= 0 </span><span class="cov8" title="1">{
|
|
name = name[lastSlash+1:]
|
|
}</span>
|
|
<span class="cov8" title="1">if period := strings.IndexByte(name, '.'); period >= 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) > 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 && len(r.Patterns) > 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("[")) && 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 > 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, &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 >= 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 < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && 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) > 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 > 0 && 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 < 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() <-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 &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 < 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 < max_ && 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 && len(n.children) > 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 > 0 && cs[newPos-1].priority < 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 && 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 < 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{&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 < len(path) </span><span class="cov8" title="1">{
|
|
path = path[i:]
|
|
c := path[0]
|
|
|
|
// '/' after param
|
|
if n.nType == param && c == '/' && 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 < 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 != ':' && c != '*' && n.nType != catchAll </span><span class="cov8" title="1">{
|
|
// []byte for proper unicode char conversion, see #65
|
|
n.indices += bytesconv.BytesToString([]byte{c})
|
|
child := &node{
|
|
fullPath: fullPath,
|
|
}
|
|
n.addChild(child)
|
|
n.incrementChildPrio(len(n.indices) - 1)
|
|
n = child
|
|
}</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) >= len(n.path) && n.path == path[:len(n.path)] &&
|
|
// Adding a child to a catchAll is not possible
|
|
n.nType != catchAll &&
|
|
// Check for longer wildcard, e.g. :name and :names
|
|
(len(n.path) >= len(path) || path[len(n.path)] == '/') </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 != ':' && 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 < 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) < 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 > 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 := &node{
|
|
nType: param,
|
|
path: wildcard,
|
|
fullPath: fullPath,
|
|
}
|
|
n.addChild(child)
|
|
n.wildChild = true
|
|
n = child
|
|
n.priority++
|
|
|
|
// if the path doesn't end with the wildcard, then there
|
|
// will be another subpath starting with '/'
|
|
if len(wildcard) < len(path) </span><span class="cov8" title="1">{
|
|
path = path[len(wildcard):]
|
|
|
|
child := &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) > 0 && 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 < 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 := &node{
|
|
wildChild: true,
|
|
nType: catchAll,
|
|
fullPath: fullPath,
|
|
}
|
|
|
|
n.addChild(child)
|
|
n.indices = string('/')
|
|
n = child
|
|
n.priority++
|
|
|
|
// second node: node holding the variable
|
|
child = &node{
|
|
path: path[i:],
|
|
nType: catchAll,
|
|
handlers: handlers,
|
|
priority: 1,
|
|
fullPath: fullPath,
|
|
}
|
|
n.children = []*node{child}
|
|
|
|
return</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) > 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: &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 > 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 == "/" && 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 < len(path) && 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) < 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 < len(path) </span><span class="cov8" title="1">{
|
|
if len(n.children) > 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 == "/" && n.handlers != nil) || (n.path == "" && 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) < 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 && path != "/" </span><span class="cov8" title="1">{
|
|
for length := len(*skippedNodes); length > 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 == "/" && n.wildChild && n.nType != root </span><span class="cov8" title="1">{
|
|
value.tsr = true
|
|
return value
|
|
}</span>
|
|
|
|
<span class="cov8" title="1">if path == "/" && 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 && n.handlers != nil) ||
|
|
(n.nType == catchAll && 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 && prefix[len(path)] == '/' &&
|
|
path == prefix[:len(prefix)-1] && n.handlers != nil)
|
|
|
|
// roll back to last valid skippedNode
|
|
if !value.tsr && path != "/" </span><span class="cov8" title="1">{
|
|
for length := len(*skippedNodes); length > 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 > 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) >= npLen && (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 && n.handlers != nil) ||
|
|
(n.nType == catchAll && 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 < 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 && path == "/" && 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 < len(path) && 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 < len(path) </span><span class="cov8" title="1">{
|
|
if len(n.children) > 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 && 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 && 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 == "/" && 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 && n.path[len(path)] == '/' &&
|
|
strings.EqualFold(path[1:], n.path[1:len(path)]) && 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(&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 > 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) == '/' && 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 < len(s); i++ </span><span class="cov8" title="1">{
|
|
if s[i] > 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>
|