Update dependencies (#333)

This commit is contained in:
tobi 2021-11-27 15:26:58 +01:00 committed by GitHub
commit 182b4eea73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
848 changed files with 377869 additions and 107280 deletions

View file

@ -1,22 +0,0 @@
run:
timeout: 5m
linters:
enable:
- gofmt
- misspell
- revive
issues:
exclude-rules:
- linters:
- structcheck
- unused
text: "`data` is unused"
- linters:
- staticcheck
text: "SA1019:"
- linters:
- revive
text: "var-naming:"
- linters:
- revive
text: "exported:"

48
vendor/github.com/gin-gonic/gin/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,48 @@
language: go
matrix:
fast_finish: true
include:
- go: 1.13.x
- go: 1.13.x
env:
- TESTTAGS=nomsgpack
- go: 1.14.x
- go: 1.14.x
env:
- TESTTAGS=nomsgpack
- go: 1.15.x
- go: 1.15.x
env:
- TESTTAGS=nomsgpack
- go: master
git:
depth: 10
before_install:
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
install:
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi
- if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
- if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
go_import_path: github.com/gin-gonic/gin
script:
- make vet
- make fmt-check
- make misspell-check
- make test
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
webhooks:
urls:
- https://webhooks.gitter.im/e/7f95bf605c4d356372f4
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View file

@ -1,5 +1,35 @@
# Gin ChangeLog
## Gin v1.7.7
### BUGFIXES
* Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862).
* Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878).
* Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843).
* Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918).
### ENHANCEMENTS
* TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819).
* TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906).
### DOCS
* NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)).
## Gin v1.7.6
### BUGFIXES
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
## Gin v1.7.4
### BUGFIXES
* bump new release to fix checksum mismatch
## Gin v1.7.3
### BUGFIXES

View file

@ -8,6 +8,6 @@
- With pull requests:
- Open your pull request against `master`
- Your pull request should have no more than two commits, if not you should squash them.
- It should pass all tests in the available continuous integration systems such as GitHub Actions.
- It should pass all tests in the available continuous integration systems such as TravisCI.
- You should add/modify tests to cover your proposed code changes.
- If your pull request contains a new feature, please document it on the README.

View file

@ -1,6 +1,5 @@
GO ?= go
GOFMT ?= gofmt "-s"
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
PACKAGES ?= $(shell $(GO) list ./...)
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
GOFILES := $(shell find . -name "*.go")
@ -68,10 +67,5 @@ misspell:
.PHONY: tools
tools:
@if [ $(GO_VERSION) -gt 15 ]; then \
$(GO) install golang.org/x/lint/golint@latest; \
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
elif [ $(GO_VERSION) -lt 16 ]; then \
$(GO) install golang.org/x/lint/golint; \
$(GO) install github.com/client9/misspell/cmd/misspell; \
fi
go install golang.org/x/lint/golint; \
go install github.com/client9/misspell/cmd/misspell;

View file

@ -2,7 +2,7 @@
<img align="right" width="159px" src="https://raw.githubusercontent.com/gin-gonic/logo/master/color.png">
[![Build Status](https://github.com/gin-gonic/gin/workflows/Run%20Tests/badge.svg?branch=master)](https://github.com/gin-gonic/gin/actions?query=branch%3Amaster)
[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
[![GoDoc](https://pkg.go.dev/badge/github.com/gin-gonic/gin?status.svg)](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
@ -23,8 +23,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Quick start](#quick-start)
- [Benchmarks](#benchmarks)
- [Gin v1. stable](#gin-v1-stable)
- [Build with jsoniter/go-json](#build-with-json-replacement)
- [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature)
- [Build with jsoniter](#build-with-jsoniter)
- [API Examples](#api-examples)
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
- [Parameters in path](#parameters-in-path)
@ -78,6 +77,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [http2 server push](#http2-server-push)
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
- [Set and get a cookie](#set-and-get-a-cookie)
- [Don't trust all proxies](#don't-trust-all-proxies)
- [Testing](#testing)
- [Users](#users)
@ -183,28 +183,13 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
- [x] Battle tested.
- [x] API frozen, new releases will not break your code.
## Build with json replacement
## Build with [jsoniter](https://github.com/json-iterator/go)
Gin uses `encoding/json` as default json package but you can change it by build from other tags.
Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
[jsoniter](https://github.com/json-iterator/go)
```sh
$ go build -tags=jsoniter .
```
[go-json](https://github.com/goccy/go-json)
```sh
$ go build -tags=go_json .
```
## Build without `MsgPack` rendering feature
Gin enables `MsgPack` rendering feature by default. But you can disable this feature by specifying `nomsgpack` build tag.
```sh
$ go build -tags=nomsgpack .
```
This is useful to reduce the binary size of executable files. See the [detail information](https://github.com/gin-gonic/gin/pull/1852).
## API Examples
@ -256,15 +241,14 @@ func main() {
// For each matched request Context will hold the route definition
router.POST("/user/:name/*action", func(c *gin.Context) {
b := c.FullPath() == "/user/:name/*action" // true
c.String(http.StatusOK, "%t", b)
c.FullPath() == "/user/:name/*action" // true
})
// This handler will add a new router for /user/groups.
// Exact routes are resolved before param routes, regardless of the order they were defined.
// Routes starting with /user/groups are never interpreted as /user/:name/... routes
router.GET("/user/groups", func(c *gin.Context) {
c.String(http.StatusOK, "The available groups are [...]")
c.String(http.StatusOK, "The available groups are [...]", name)
})
router.Run(":8080")
@ -703,7 +687,7 @@ func main() {
// Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>manu</user>
// <user>user</user>
// <password>123</password>
// </root>)
router.POST("/loginXML", func(c *gin.Context) {
@ -942,7 +926,7 @@ func main() {
route.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err.Error()})
c.JSON(400, gin.H{"msg": err})
return
}
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
@ -2022,61 +2006,6 @@ enough to call binding at once.
can be called by `c.ShouldBind()` multiple times without any damage to
performance (See [#1341](https://github.com/gin-gonic/gin/pull/1341)).
### Bind form-data request with custom struct and custom tag
```go
const (
customerTag = "url"
defaultMemory = 32 << 20
)
type customerBinding struct {}
func (customerBinding) Name() string {
return "form"
}
func (customerBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
return err
}
return validate(obj)
}
func validate(obj interface{}) error {
if binding.Validator == nil {
return nil
}
return binding.Validator.ValidateStruct(obj)
}
// Now we can do this!!!
// FormA is a external type that we can't modify it's tag
type FormA struct {
FieldA string `url:"field_a"`
}
func ListHandler(s *Service) func(ctx *gin.Context) {
return func(ctx *gin.Context) {
var urlBinding = customerBinding{}
var opt FormA
err := ctx.MustBindWith(&opt, urlBinding)
if err != nil {
...
}
...
}
}
```
### http2 server push
http.Pusher is supported only **go1.8+**. See the [golang blog](https://blog.golang.org/h2push) for detail information.
@ -2202,11 +2131,17 @@ Gin lets you specify which headers to hold the real client IP (if any),
as well as specifying which proxies (or direct clients) you trust to
specify one of these headers.
The `TrustedProxies` slice on your `gin.Engine` specifes network addresses or
network CIDRs from where clients which their request headers related to client
Use function `SetTrustedProxies()` on your `gin.Engine` to specify network addresses
or network CIDRs from where clients which their request headers related to client
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
IPv6 CIDRs.
**Attention:** Gin trust all proxies by default if you don't specify a trusted
proxy using the function above, **this is NOT safe**. At the same time, if you don't
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
then `Context.ClientIP()` will return the remote address directly to avoid some
unnecessary computation.
```go
import (
"fmt"
@ -2217,7 +2152,7 @@ import (
func main() {
router := gin.Default()
router.TrustedProxies = []string{"192.168.1.2"}
router.SetTrustedProxies([]string{"192.168.1.2"})
router.GET("/", func(c *gin.Context) {
// If the client is 192.168.1.2, use the X-Forwarded-For
@ -2230,6 +2165,34 @@ func main() {
}
```
**Notice:** If you are using a CDN service, you can set the `Engine.TrustedPlatform`
to skip TrustedProxies check, it has a higher priority than TrustedProxies.
Look at the example below:
```go
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// Use predefined header gin.PlatformXXX
router.TrustedPlatform = gin.PlatformGoogleAppEngine
// Or set your own trusted request header for another trusted proxy service
// Don't set it to any suspect request header, it's unsafe
router.TrustedPlatform = "X-CDN-IP"
router.GET("/", func(c *gin.Context) {
// If you set TrustedPlatform, ClientIP() will resolve the
// corresponding header and return IP directly
fmt.Printf("ClientIP: %s\n", c.ClientIP())
})
router.Run()
}
```
## Testing
The `net/http/httptest` package is preferable way for HTTP testing.
@ -2287,4 +2250,3 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.

View file

@ -49,7 +49,7 @@ type BindingUri interface {
// 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.
// https://github.com/go-playground/validator/tree/v8.18.2.
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.
@ -65,7 +65,7 @@ type StructValidator interface {
}
// Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
// under the hood.
var Validator StructValidator = &defaultValidator{}

View file

@ -47,7 +47,7 @@ type BindingUri interface {
// 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.
// https://github.com/go-playground/validator/tree/v8.18.2.
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 not a struct, any validation should be skipped and nil must be returned.
@ -62,7 +62,7 @@ type StructValidator interface {
}
// Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
// under the hood.
var Validator StructValidator = &defaultValidator{}

View file

@ -20,27 +20,15 @@ type defaultValidator struct {
type sliceValidateError []error
// Error concatenates all error elements in sliceValidateError into a single string separated by \n.
func (err sliceValidateError) Error() string {
n := len(err)
switch n {
case 0:
return ""
default:
var b strings.Builder
if err[0] != nil {
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
var errMsgs []string
for i, e := range err {
if e == nil {
continue
}
if n > 1 {
for i := 1; i < n; i++ {
if err[i] != nil {
b.WriteString("\n")
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
}
}
}
return b.String()
errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error()))
}
return strings.Join(errMsgs, "\n")
}
var _ StructValidator = &defaultValidator{}
@ -83,7 +71,7 @@ func (v *defaultValidator) validateStruct(obj interface{}) error {
// 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
// https://godoc.org/gopkg.in/go-playground/validator.v8
func (v *defaultValidator) Engine() interface{} {
v.lazyinit()
return v.validate

View file

@ -22,8 +22,10 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := req.ParseForm(); err != nil {
return err
}
if err := req.ParseMultipartForm(defaultMemory); err != nil && err != http.ErrNotMultipart {
return err
if err := req.ParseMultipartForm(defaultMemory); err != nil {
if err != http.ErrNotMultipart {
return err
}
}
if err := mapForm(obj, req.Form); err != nil {
return err

View file

@ -16,17 +16,9 @@ import (
"github.com/gin-gonic/gin/internal/json"
)
var (
errUnknownType = errors.New("unknown type")
var errUnknownType = errors.New("unknown type")
// ErrConvertMapStringSlice can not covert 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 interface{}, m map[string][]string) error {
func mapUri(ptr interface{}, m map[string][]string) error {
return mapFormByTag(ptr, m, "uri")
}
@ -34,10 +26,6 @@ func mapForm(ptr interface{}, form map[string][]string) error {
return mapFormByTag(ptr, form, "form")
}
func MapFormWithTag(ptr interface{}, form map[string][]string, tag string) error {
return mapFormByTag(ptr, form, tag)
}
var emptyField = reflect.StructField{}
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
@ -83,7 +71,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
return false, nil
}
vKind := value.Kind()
var vKind = value.Kind()
if vKind == reflect.Ptr {
var isNew bool
@ -121,7 +109,7 @@ func mapping(value reflect.Value, field reflect.StructField, setter setter, tag
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
ok, err := mapping(value.Field(i), sf, setter, tag)
ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
if err != nil {
return false, err
}
@ -210,7 +198,7 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
case reflect.Int64:
switch value.Interface().(type) {
case time.Duration:
return setTimeDuration(val, value)
return setTimeDuration(val, value, field)
}
return setIntField(val, 64, value)
case reflect.Uint:
@ -310,6 +298,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
t := time.Unix(tv/int64(d), tv%int64(d))
value.Set(reflect.ValueOf(t))
return nil
}
if val == "" {
@ -359,7 +348,7 @@ func setSlice(vals []string, value reflect.Value, field reflect.StructField) err
return nil
}
func setTimeDuration(val string, value reflect.Value) error {
func setTimeDuration(val string, value reflect.Value, field reflect.StructField) error {
d, err := time.ParseDuration(val)
if err != nil {
return err
@ -382,7 +371,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
if el.Kind() == reflect.Slice {
ptrMap, ok := ptr.(map[string][]string)
if !ok {
return ErrConvertMapStringSlice
return errors.New("cannot convert to map slices of strings")
}
for k, v := range form {
ptrMap[k] = v
@ -393,7 +382,7 @@ func setFormMap(ptr interface{}, form map[string][]string) error {
ptrMap, ok := ptr.(map[string]string)
if !ok {
return ErrConvertToMapString
return errors.New("cannot convert to map of strings")
}
for k, v := range form {
ptrMap[k] = v[len(v)-1] // pick last

View file

@ -29,6 +29,6 @@ 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) {
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
}

View file

@ -6,7 +6,7 @@ package binding
import (
"bytes"
"errors"
"fmt"
"io"
"net/http"
@ -32,7 +32,7 @@ func (jsonBinding) Name() string {
func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
if req == nil || req.Body == nil {
return errors.New("invalid request")
return fmt.Errorf("invalid request")
}
return decodeJSON(req.Body, obj)
}

View file

@ -15,16 +15,8 @@ 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) {
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) {
if files := r.MultipartForm.File[key]; len(files) != 0 {
return setByMultipartFormFile(value, field, files)
}
@ -57,12 +49,12 @@ func setByMultipartFormFile(value reflect.Value, field reflect.StructField, file
case reflect.Array:
return setArrayOfMultipartFormFiles(value, field, files)
}
return false, ErrMultiFileHeader
return false, errors.New("unsupported field type for multipart.FileHeader")
}
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSetted bool, err error) {
if value.Len() != len(files) {
return false, ErrMultiFileHeaderLenInvalid
return false, errors.New("unsupported len of array for []*multipart.FileHeader")
}
for i := range files {
setted, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])

View file

@ -5,11 +5,10 @@
package binding
import (
"errors"
"io/ioutil"
"net/http"
"google.golang.org/protobuf/proto"
"github.com/golang/protobuf/proto"
)
type protobufBinding struct{}
@ -27,11 +26,7 @@ func (b protobufBinding) Bind(req *http.Request, obj interface{}) error {
}
func (protobufBinding) BindBody(body []byte, obj interface{}) error {
msg, ok := obj.(proto.Message)
if !ok {
return errors.New("obj is not ProtoMessage")
}
if err := proto.Unmarshal(body, msg); err != nil {
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
return err
}
// Here it's same to return validate(obj), but util now we can't add

View file

@ -11,7 +11,7 @@ func (uriBinding) Name() string {
}
func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
if err := mapURI(obj, m); err != nil {
if err := mapUri(obj, m); err != nil {
return err
}
return validate(obj)

View file

@ -40,8 +40,7 @@ const (
// BodyBytesKey indicates a default body bytes key.
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
// abortIndex represents a typical value used in abort functions.
const abortIndex int8 = math.MaxInt8 >> 1
const abortIndex int8 = math.MaxInt8 / 2
// 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.
@ -55,8 +54,9 @@ type Context struct {
index int8
fullPath string
engine *Engine
params *Params
engine *Engine
params *Params
skippedNodes *[]skippedNode
// This mutex protect Keys map
mu sync.RWMutex
@ -88,17 +88,18 @@ type Context struct {
func (c *Context) reset() {
c.Writer = &c.writermem
c.Params = c.Params[:0]
c.Params = c.Params[0:0]
c.handlers = nil
c.index = -1
c.fullPath = ""
c.Keys = nil
c.Errors = c.Errors[:0]
c.Errors = c.Errors[0:0]
c.Accepted = nil
c.queryCache = nil
c.formCache = nil
*c.params = (*c.params)[:0]
*c.skippedNodes = (*c.skippedNodes)[:0]
}
// Copy returns a copy of the current context that can be safely used outside the request's scope.
@ -383,15 +384,6 @@ func (c *Context) Param(key string) string {
return c.Params.ByName(key)
}
// AddParam adds param to context and
// replaces path param key with given value for e2e testing purposes
// Example Route: "/user/:id"
// AddParam("id", 1)
// Result: "/user/1"
func (c *Context) AddParam(key, value string) {
c.Params = append(c.Params, Param{Key: key, Value: value})
}
// Query returns the keyed url query value if it exists,
// otherwise it returns an empty string `("")`.
// It is shortcut for `c.Request.URL.Query().Get(key)`
@ -400,9 +392,9 @@ func (c *Context) AddParam(key, value string) {
// c.Query("name") == "Manu"
// c.Query("value") == ""
// c.Query("wtf") == ""
func (c *Context) Query(key string) (value string) {
value, _ = c.GetQuery(key)
return
func (c *Context) Query(key string) string {
value, _ := c.GetQuery(key)
return value
}
// DefaultQuery returns the keyed url query value if it exists,
@ -436,9 +428,9 @@ func (c *Context) GetQuery(key string) (string, bool) {
// QueryArray returns a slice of strings for a given query key.
// The length of the slice depends on the number of params with the given key.
func (c *Context) QueryArray(key string) (values []string) {
values, _ = c.GetQueryArray(key)
return
func (c *Context) QueryArray(key string) []string {
values, _ := c.GetQueryArray(key)
return values
}
func (c *Context) initQueryCache() {
@ -453,16 +445,18 @@ func (c *Context) initQueryCache() {
// 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) {
func (c *Context) GetQueryArray(key string) ([]string, bool) {
c.initQueryCache()
values, ok = c.queryCache[key]
return
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
return values, true
}
return []string{}, false
}
// QueryMap returns a map for a given query key.
func (c *Context) QueryMap(key string) (dicts map[string]string) {
dicts, _ = c.GetQueryMap(key)
return
func (c *Context) QueryMap(key string) map[string]string {
dicts, _ := c.GetQueryMap(key)
return dicts
}
// GetQueryMap returns a map for a given query key, plus a boolean value
@ -474,9 +468,9 @@ func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
// PostForm returns the specified key from a POST urlencoded form or multipart form
// when it exists, otherwise it returns an empty string `("")`.
func (c *Context) PostForm(key string) (value string) {
value, _ = c.GetPostForm(key)
return
func (c *Context) PostForm(key string) string {
value, _ := c.GetPostForm(key)
return value
}
// DefaultPostForm returns the specified key from a POST urlencoded form or multipart form
@ -505,9 +499,9 @@ func (c *Context) GetPostForm(key string) (string, bool) {
// PostFormArray returns a slice of strings for a given form key.
// The length of the slice depends on the number of params with the given key.
func (c *Context) PostFormArray(key string) (values []string) {
values, _ = c.GetPostFormArray(key)
return
func (c *Context) PostFormArray(key string) []string {
values, _ := c.GetPostFormArray(key)
return values
}
func (c *Context) initFormCache() {
@ -525,16 +519,18 @@ func (c *Context) initFormCache() {
// 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) {
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
c.initFormCache()
values, ok = c.formCache[key]
return
if values := c.formCache[key]; len(values) > 0 {
return values, true
}
return []string{}, false
}
// PostFormMap returns a map for a given form key.
func (c *Context) PostFormMap(key string) (dicts map[string]string) {
dicts, _ = c.GetPostFormMap(key)
return
func (c *Context) PostFormMap(key string) map[string]string {
dicts, _ := c.GetPostFormMap(key)
return dicts
}
// GetPostFormMap returns a map for a given form key, plus a boolean value
@ -732,20 +728,16 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
return bb.BindBody(body, obj)
}
// ClientIP implements a best effort algorithm to return the real client IP.
// ClientIP implements one best effort algorithm to return the real client IP.
// It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
// If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,
// 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 form Request.RemoteAddr) is returned.
func (c *Context) ClientIP() string {
// Check if we're running on a trusted platform
switch c.engine.TrustedPlatform {
case PlatformGoogleAppEngine:
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
return addr
}
case PlatformCloudflare:
if addr := c.requestHeader("CF-Connecting-IP"); addr != "" {
// Check if we're running on a trusted platform, continue running backwards if error
if c.engine.TrustedPlatform != "" {
// Developers can define their own header of Trusted Platform or use predefined constants
if addr := c.requestHeader(c.engine.TrustedPlatform); addr != "" {
return addr
}
}
@ -765,7 +757,7 @@ func (c *Context) ClientIP() string {
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
for _, headerName := range c.engine.RemoteIPHeaders {
ip, valid := validateHeader(c.requestHeader(headerName))
ip, valid := c.engine.validateHeader(c.requestHeader(headerName))
if valid {
return ip
}
@ -774,10 +766,21 @@ func (c *Context) ClientIP() string {
return remoteIP.String()
}
func (e *Engine) isTrustedProxy(ip net.IP) bool {
if e.trustedCIDRs != nil {
for _, cidr := range e.trustedCIDRs {
if cidr.Contains(ip) {
return true
}
}
}
return false
}
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
// 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 in Engine.TrustedProxies
// defined by Engine.SetTrustedProxies()
func (c *Context) RemoteIP() (net.IP, bool) {
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
if err != nil {
@ -788,35 +791,25 @@ func (c *Context) RemoteIP() (net.IP, bool) {
return nil, false
}
if c.engine.trustedCIDRs != nil {
for _, cidr := range c.engine.trustedCIDRs {
if cidr.Contains(remoteIP) {
return remoteIP, true
}
}
}
return remoteIP, false
return remoteIP, c.engine.isTrustedProxy(remoteIP)
}
func validateHeader(header string) (clientIP string, valid bool) {
func (e *Engine) validateHeader(header string) (clientIP string, valid bool) {
if header == "" {
return "", false
}
items := strings.Split(header, ",")
for i, ipStr := range items {
ipStr = strings.TrimSpace(ipStr)
for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil {
return "", false
}
// We need to return the first IP in the list, but,
// we should not early return since we need to validate that
// the rest of the header is syntactically valid
if i == 0 {
clientIP = ipStr
valid = true
// X-Forwarded-For is appended by proxy
// Check IPs in reverse order and stop when find untrusted proxy
if (i == 0) || (!e.isTrustedProxy(ip)) {
return ipStr, true
}
}
return
@ -1166,28 +1159,22 @@ func (c *Context) SetAccepted(formats ...string) {
/***** GOLANG.ORG/X/NET/CONTEXT *****/
/************************************/
// Deadline returns that there is no deadline (ok==false) when c.Request has no Context.
// Deadline always returns that there is no deadline (ok==false),
// maybe you want to use Request.Context().Deadline() instead.
func (c *Context) Deadline() (deadline time.Time, ok bool) {
if c.Request == nil || c.Request.Context() == nil {
return
}
return c.Request.Context().Deadline()
return
}
// Done returns nil (chan which will wait forever) when c.Request has no Context.
// Done always returns nil (chan which will wait forever),
// if you want to abort your work when the connection was closed
// you should use Request.Context().Done() instead.
func (c *Context) Done() <-chan struct{} {
if c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Done()
return nil
}
// Err returns nil when c.Request has no Context.
// Err always returns nil, maybe you want to use Request.Context().Err() instead.
func (c *Context) Err() error {
if c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Err()
return nil
}
// Value returns the value associated with this context for key, or nil
@ -1198,12 +1185,8 @@ func (c *Context) Value(key interface{}) interface{} {
return c.Request
}
if keyAsString, ok := key.(string); ok {
if val, exists := c.Get(keyAsString); exists {
return val
}
val, _ := c.Get(keyAsString)
return val
}
if c.Request == nil || c.Request.Context() == nil {
return nil
}
return c.Request.Context().Value(key)
return nil
}

View file

@ -95,7 +95,9 @@ at initialization. ie. before any route is registered or the router is listening
}
func debugPrintError(err error) {
if err != nil && IsDebugging() {
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
if err != nil {
if IsDebugging() {
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
}
}
}

View file

@ -17,7 +17,7 @@ type neuteredReaddirFile struct {
http.File
}
// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
// Dir returns a 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.

View file

@ -11,6 +11,7 @@ import (
"net/http"
"os"
"path"
"reflect"
"strings"
"sync"
@ -27,6 +28,8 @@ var (
var defaultPlatform string
var defaultTrustedCIDRs = []*net.IPNet{{IP: net.IP{0x0, 0x0, 0x0, 0x0}, Mask: net.IPMask{0x0, 0x0, 0x0, 0x0}}} // 0.0.0.0/0
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
@ -56,10 +59,10 @@ type RoutesInfo []RouteInfo
const (
// When running on Google App Engine. Trust X-Appengine-Remote-Addr
// for determining the client's IP
PlatformGoogleAppEngine = "google-app-engine"
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
// When using Cloudflare's CDN. Trust CF-Connecting-IP for determining
// the client's IP
PlatformCloudflare = "cloudflare"
PlatformCloudflare = "CF-Connecting-IP"
)
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
@ -119,15 +122,9 @@ type Engine struct {
// 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 `(*gin.Engine).TrustedProxies`.
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
RemoteIPHeaders []string
// 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 []string
// 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
@ -147,6 +144,8 @@ type Engine struct {
pool sync.Pool
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
@ -174,7 +173,6 @@ func New() *Engine {
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedProxies: []string{"0.0.0.0/0"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
@ -183,6 +181,8 @@ func New() *Engine {
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
@ -201,7 +201,8 @@ func Default() *Engine {
func (engine *Engine) allocateContext() *Context {
v := make(Params, 0, engine.maxParams)
return &Context{engine: engine, params: &v}
skippedNodes := make([]skippedNode, 0, engine.maxSections)
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
}
// Delims sets template left and right delims and returns a Engine instance.
@ -264,7 +265,7 @@ func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
engine.rebuild404Handlers()
}
// NoMethod sets the handlers called when... TODO.
// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
engine.noMethod = handlers
engine.rebuild405Handlers()
@ -307,6 +308,10 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
engine.maxSections = sectionsCount
}
}
// Routes returns a slice of registered routes, including some useful information, such as:
@ -341,9 +346,9 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
err = engine.parseTrustedProxies()
if err != nil {
return err
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
@ -353,12 +358,12 @@ func (engine *Engine) Run(addr ...string) (err error) {
}
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
if engine.TrustedProxies == nil {
if engine.trustedProxies == nil {
return nil, nil
}
cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies))
for _, trustedProxy := range engine.TrustedProxies {
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
for _, trustedProxy := range engine.trustedProxies {
if !strings.Contains(trustedProxy, "/") {
ip := parseIP(trustedProxy)
if ip == nil {
@ -381,13 +386,25 @@ func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
return cidr, nil
}
// SetTrustedProxies set Engine.TrustedProxies
// SetTrustedProxies set a list of network origins (IPv4 addresses,
// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
// request's headers that contain alternative client IP when
// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
// feature is enabled by default, and it also trusts all proxies
// by default. If you want to disable this feature, use
// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
// return the remote address directly.
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
engine.TrustedProxies = trustedProxies
engine.trustedProxies = trustedProxies
return engine.parseTrustedProxies()
}
// parseTrustedProxies parse Engine.TrustedProxies to Engine.trustedCIDRs
// isUnsafeTrustedProxies compares Engine.trustedCIDRs and defaultTrustedCIDRs, it's not safe if equal (returns true)
func (engine *Engine) isUnsafeTrustedProxies() bool {
return reflect.DeepEqual(engine.trustedCIDRs, defaultTrustedCIDRs)
}
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
func (engine *Engine) parseTrustedProxies() error {
trustedCIDRs, err := engine.prepareTrustedCIDRs()
engine.trustedCIDRs = trustedCIDRs
@ -415,9 +432,9 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
debugPrint("Listening and serving HTTPS on %s\n", addr)
defer func() { debugPrintError(err) }()
err = engine.parseTrustedProxies()
if err != nil {
return err
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine)
@ -431,9 +448,9 @@ func (engine *Engine) RunUnix(file string) (err error) {
debugPrint("Listening and serving HTTP on unix:/%s", file)
defer func() { debugPrintError(err) }()
err = engine.parseTrustedProxies()
if err != nil {
return err
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
listener, err := net.Listen("unix", file)
@ -454,9 +471,9 @@ func (engine *Engine) RunFd(fd int) (err error) {
debugPrint("Listening and serving HTTP on fd@%d", fd)
defer func() { debugPrintError(err) }()
err = engine.parseTrustedProxies()
if err != nil {
return err
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
@ -475,9 +492,9 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
defer func() { debugPrintError(err) }()
err = engine.parseTrustedProxies()
if err != nil {
return err
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
err = http.Serve(listener, engine)
@ -528,7 +545,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, unescape)
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
@ -539,7 +556,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if httpMethod != "CONNECT" && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
@ -556,7 +573,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, unescape); value.handlers != nil {
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = engine.allNoMethod
serveError(c, http.StatusMethodNotAllowed, default405Body)
return

View file

@ -1,23 +0,0 @@
// Copyright 2017 Bo-Yi Wu. 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 go_json
// +build go_json
package json
import json "github.com/goccy/go-json"
var (
// Marshal is exported by gin/json package.
Marshal = json.Marshal
// Unmarshal is exported by gin/json package.
Unmarshal = json.Unmarshal
// MarshalIndent is exported by gin/json package.
MarshalIndent = json.MarshalIndent
// NewDecoder is exported by gin/json package.
NewDecoder = json.NewDecoder
// NewEncoder is exported by gin/json package.
NewEncoder = json.NewEncoder
)

View file

@ -2,8 +2,8 @@
// 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
// +build !jsoniter,!go_json
//go:build !jsoniter
// +build !jsoniter
package json

View file

@ -138,7 +138,8 @@ var defaultLogFormatter = func(param LogFormatterParams) string {
}
if param.Latency > time.Minute {
param.Latency = param.Latency.Truncate(time.Second)
// Truncate in a golang < 1.8 safe way
param.Latency = param.Latency - param.Latency%time.Second
}
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
param.TimeStamp.Format("2006/01/02 - 15:04:05"),

View file

@ -41,10 +41,8 @@ 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 = debugCode
modeName = DebugMode
)
var ginMode = debugCode
var modeName = DebugMode
func init() {
mode := os.Getenv(EnvGinMode)

View file

@ -34,7 +34,7 @@ func Recovery() HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter)
}
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
return RecoveryWithWriter(DefaultErrorWriter, handle)
}
@ -165,7 +165,7 @@ func function(pc uintptr) []byte {
return name
}
// timeFormat returns a customized time string for logger.
func timeFormat(t time.Time) string {
return t.Format("2006/01/02 - 15:04:05")
timeString := t.Format("2006/01/02 - 15:04:05")
return timeString
}

View file

@ -46,11 +46,9 @@ type PureJSON struct {
Data interface{}
}
var (
jsonContentType = []string{"application/json; charset=utf-8"}
jsonpContentType = []string{"application/javascript; charset=utf-8"}
jsonASCIIContentType = []string{"application/json"}
)
var jsonContentType = []string{"application/json; charset=utf-8"}
var jsonpContentType = []string{"application/javascript; charset=utf-8"}
var jsonAsciiContentType = []string{"application/json"}
// Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) (err error) {
@ -102,7 +100,8 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
// if the jsonBytes is array values
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
bytesconv.StringToBytes("]")) {
if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
if err != nil {
return err
}
}
@ -129,19 +128,20 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
}
callback := template.JSEscapeString(r.Callback)
if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
_, err = w.Write(bytesconv.StringToBytes(callback))
if err != nil {
return err
}
if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
_, err = w.Write(bytesconv.StringToBytes("("))
if err != nil {
return err
}
if _, err = w.Write(ret); err != nil {
_, err = w.Write(ret)
if err != nil {
return err
}
if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
_, err = w.Write(bytesconv.StringToBytes(");"))
if err != nil {
return err
}
@ -176,7 +176,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
// WriteContentType (AsciiJSON) writes JSON ContentType.
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonASCIIContentType)
writeContentType(w, jsonAsciiContentType)
}
// Render (PureJSON) writes custom ContentType and encodes the given interface object.

View file

@ -13,8 +13,6 @@ import (
"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{}
)

View file

@ -7,7 +7,7 @@ package render
import (
"net/http"
"google.golang.org/protobuf/proto"
"github.com/golang/protobuf/proto"
)
// ProtoBuf contains the given interface object.

View file

@ -11,11 +11,6 @@ import (
"strings"
)
var (
// reg match english letters for http method name
regEnLetter = regexp.MustCompile("^[A-Z]+$")
)
// IRouter defines all router handle interface includes single and group router.
type IRouter interface {
IRoutes
@ -92,7 +87,7 @@ func (group *RouterGroup) handle(httpMethod, relativePath string, handlers Handl
// frequently used, non-standardized or custom methods (e.g. for internal
// communication with a proxy).
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
if matched := regEnLetter.MatchString(httpMethod); !matched {
if matches, err := regexp.MatchString("^[A-Z]+$", httpMethod); !matches || err != nil {
panic("http method " + httpMethod + " is not valid")
}
return group.handle(httpMethod, relativePath, handlers)
@ -214,7 +209,9 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)

View file

@ -17,6 +17,7 @@ import (
var (
strColon = []byte(":")
strStar = []byte("*")
strSlash = []byte("/")
)
// Param is a single URL parameter, consisting of a key and a value.
@ -30,8 +31,8 @@ type Param struct {
// 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 .
// Get 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) Get(name string) (string, bool) {
for _, entry := range ps {
if entry.Key == name {
@ -98,10 +99,16 @@ func countParams(path string) uint16 {
return n
}
func countSections(path string) uint16 {
s := bytesconv.StringToBytes(path)
return uint16(bytes.Count(s, strSlash))
}
type nodeType uint8
const (
root nodeType = iota + 1
static nodeType = iota // default
root
param
catchAll
)
@ -393,16 +400,19 @@ type nodeValue struct {
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, unescape bool) (value nodeValue) {
var (
skippedPath string
latestNode = n // Caching the latest node
)
func (n *node) getValue(path string, params *Params, skippedNodes *[]skippedNode, unescape bool) (value nodeValue) {
var globalParamsCount int16
walk: // Outer loop for walking the tree
for {
@ -417,15 +427,20 @@ walk: // Outer loop for walking the tree
if c == idxc {
// strings.HasPrefix(n.children[len(n.children)-1].path, ":") == n.wildChild
if n.wildChild {
skippedPath = prefix + path
latestNode = &node{
path: n.path,
wildChild: n.wildChild,
nType: n.nType,
priority: n.priority,
children: n.children,
handlers: n.handlers,
fullPath: n.fullPath,
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,
}
}
@ -433,15 +448,26 @@ walk: // Outer loop for walking the tree
continue walk
}
}
// 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 be equal to the latest matching node
matched := path != "/" && !n.wildChild
if matched {
n = latestNode
}
// If there is no wildcard pattern, recommend a redirection
if !n.wildChild {
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
// the current node needs to roll back to last vaild skippedNode
if path != "/" {
for l := len(*skippedNodes); l > 0; {
skippedNode := (*skippedNodes)[l-1]
*skippedNodes = (*skippedNodes)[:l-1]
if strings.HasSuffix(skippedNode.path, path) {
path = skippedNode.path
n = skippedNode.node
if value.params != nil {
*value.params = (*value.params)[:skippedNode.paramsCount]
}
globalParamsCount = skippedNode.paramsCount
continue walk
}
}
}
// Nothing found.
// We can recommend to redirect to the same URL without a
// trailing slash if a leaf exists for that path.
@ -451,18 +477,12 @@ walk: // Outer loop for walking the tree
// Handle wildcard child, which is always at the end of the array
n = n.children[len(n.children)-1]
globalParamsCount++
switch n.nType {
case param:
// fix truncate the parameter
// tree_test.go line: 204
if matched {
path = prefix + path
// The saved path is used after the prefix route is intercepted by matching
if n.indices == "/" {
path = skippedPath[1:]
}
}
// Find param end (either '/' or path end)
end := 0
@ -548,9 +568,22 @@ walk: // Outer loop for walking the tree
if path == prefix {
// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
// the current node needs to be equal to the latest matching node
if latestNode.wildChild && n.handlers == nil && path != "/" {
n = latestNode.children[len(latestNode.children)-1]
// the current node needs to roll back to last vaild skippedNode
if n.handlers == nil && path != "/" {
for l := len(*skippedNodes); l > 0; {
skippedNode := (*skippedNodes)[l-1]
*skippedNodes = (*skippedNodes)[:l-1]
if strings.HasSuffix(skippedNode.path, path) {
path = skippedNode.path
n = skippedNode.node
if value.params != nil {
*value.params = (*value.params)[:skippedNode.paramsCount]
}
globalParamsCount = skippedNode.paramsCount
continue walk
}
}
// n = latestNode.children[len(latestNode.children)-1]
}
// We should have reached the node containing the handle.
// Check if this node has a handle registered.
@ -581,25 +614,29 @@ walk: // Outer loop for walking the tree
return
}
if path != "/" && len(skippedPath) > 0 && strings.HasSuffix(skippedPath, path) {
path = skippedPath
// Reduce the number of cycles
n, latestNode = latestNode, n
// skippedPath cannot execute
// example:
// * /:cc/cc
// call /a/cc expectations:match/200 Actual:match/200
// call /a/dd expectations:unmatch/404 Actual: panic
// call /addr/dd/aa expectations:unmatch/404 Actual: panic
// skippedPath: It can only be executed if the secondary route is not found
skippedPath = ""
continue walk
}
// Nothing found. We can recommend to redirect to the same URL with an
// extra trailing slash if a leaf exists for that path
value.tsr = path == "/" ||
(len(prefix) == len(path)+1 && n.handlers != nil)
(len(prefix) == len(path)+1 && prefix[len(path)] == '/' &&
path == prefix[:len(prefix)-1] && n.handlers != nil)
// roll back to last valid skippedNode
if !value.tsr && path != "/" {
for l := len(*skippedNodes); l > 0; {
skippedNode := (*skippedNodes)[l-1]
*skippedNodes = (*skippedNodes)[:l-1]
if strings.HasSuffix(skippedNode.path, path) {
path = skippedNode.path
n = skippedNode.node
if value.params != nil {
*value.params = (*value.params)[:skippedNode.paramsCount]
}
globalParamsCount = skippedNode.paramsCount
continue walk
}
}
}
return
}
}

View file

@ -5,4 +5,4 @@
package gin
// Version is the current gin framework's version.
const Version = "v1.7.3"
const Version = "v1.7.7"