Update go dependencies (#1972)

This commit is contained in:
Dmitry Verkhoturov
2025-12-03 19:47:01 -06:00
committed by GitHub
parent b451142790
commit 564e8ff316
241 changed files with 18667 additions and 2955 deletions
@@ -25,6 +25,9 @@ This is a generic middleware to rate-limit HTTP requests.
**v7.x.x:** Replaced `time/rate` with `embedded time/rate` so that we can support more rate limit headers.
**v8.x.x:** Address `RemoteIP` vulnerability concern by replacing `SetIPLookups` with `SetIPLookup`, an explicit way to pick the IP address.
## Five Minute Tutorial
```go
@@ -33,7 +36,8 @@ package main
import (
"net/http"
"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth/v8"
"github.com/didip/tollbooth/v8/limiter"
)
func HelloHandler(w http.ResponseWriter, req *http.Request) {
@@ -42,7 +46,15 @@ func HelloHandler(w http.ResponseWriter, req *http.Request) {
func main() {
// Create a request limiter per handler.
http.Handle("/", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, nil), HelloHandler))
lmt := tollbooth.NewLimiter(1, nil)
// New in version >= 8, you must explicitly define how to pick the IP address.
lmt.SetIPLookup(limiter.IPLookup{
Name: "X-Real-IP",
IndexFromRight: 0,
})
http.Handle("/", tollbooth.LimitFuncHandler(lmt, HelloHandler))
http.ListenAndServe(":12345", nil)
}
```
@@ -54,8 +66,8 @@ func main() {
import (
"time"
"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth/v7/limiter"
"github.com/didip/tollbooth/v8"
"github.com/didip/tollbooth/v8/limiter"
)
lmt := tollbooth.NewLimiter(1, nil)
@@ -66,10 +78,24 @@ func main() {
// every token bucket in it will expire 1 hour after it was initially set.
lmt = tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
// Configure list of places to look for IP address.
// By default it's: "RemoteAddr", "X-Forwarded-For", "X-Real-IP"
// If your application is behind a proxy, set "X-Forwarded-For" first.
lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"})
// New in version >= 8, you must explicitly define how to pick the IP address.
// If IP address cannot be found, rate limiter will not be activated.
lmt.SetIPLookup(limiter.IPLookup{
// The name of lookup method.
// Possible options are: RemoteAddr, X-Forwarded-For, X-Real-IP, CF-Connecting-IP
// All other headers are considered unknown and will be ignored.
Name: "X-Real-IP",
// The index position to pick the ip address from a comma separated list.
// The index goes from right to left.
//
// When there are multiple of the same headers,
// we will concat them together in the order of first to last seen.
// And then we pick the IP using this index position.
IndexFromRight: 0,
})
// In version >= 8, lmt.SetIPLookups and lmt.GetIPLookups are removed.
// Limit only GET and POST requests.
lmt.SetMethods([]string{"GET", "POST"})
@@ -89,8 +115,7 @@ func main() {
lmt.RemoveHeaderEntries("X-Access-Token", []string{"limitless-token"})
// By the way, the setters are chainable. Example:
lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
SetMethods([]string{"GET", "POST"}).
lmt.SetMethods([]string{"GET", "POST"}).
SetBasicAuthUsers([]string{"sansa"}).
SetBasicAuthUsers([]string{"tyrion"})
```
@@ -137,6 +162,12 @@ func main() {
```go
lmt := tollbooth.NewLimiter(1, nil)
// New in version >= 8, you must explicitly define how to pick the IP address.
lmt.SetIPLookup(limiter.IPLookup{
Name: "X-Forwarded-For",
IndexFromRight: 0,
})
// Set a custom message.
lmt.SetMessage("You have reached maximum request limit.")
@@ -5,6 +5,8 @@ import (
"net"
"net/http"
"strings"
"github.com/didip/tollbooth/v8/limiter"
)
// StringInSlice finds needle in a slice of strings.
@@ -17,38 +19,35 @@ func StringInSlice(sliceString []string, needle string) bool {
return false
}
// RemoteIP finds IP Address given http.Request struct.
func RemoteIP(ipLookups []string, forwardedForIndexFromBehind int, r *http.Request) string {
realIP := r.Header.Get("X-Real-IP")
forwardedFor := r.Header.Get("X-Forwarded-For")
for _, lookup := range ipLookups {
if lookup == "RemoteAddr" {
// 1. Cover the basic use cases for both ipv4 and ipv6
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
// 2. Upon error, just return the remote addr.
return r.RemoteAddr
}
return ip
// RemoteIPFromIPLookup picks an ip address explicitly from limiter.IPLookup criteria.
// This function is intended to replace RemoteIP function.
func RemoteIPFromIPLookup(ipLookup limiter.IPLookup, r *http.Request) string {
switch ipLookup.Name {
case "RemoteAddr":
// 1. Cover the basic use cases for both ipv4 and ipv6
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
// 2. Upon error, just return the remote addr.
return r.RemoteAddr
}
if lookup == "X-Forwarded-For" && forwardedFor != "" {
// X-Forwarded-For is potentially a list of addresses separated with ","
parts := strings.Split(forwardedFor, ",")
for i, p := range parts {
parts[i] = strings.TrimSpace(p)
}
return ip
partIndex := len(parts) - 1 - forwardedForIndexFromBehind
if partIndex < 0 {
partIndex = 0
}
case "X-Forwarded-For", "X-Real-IP", "CF-Connecting-IP":
ipAddrListCommaSeparated := r.Header.Values(ipLookup.Name)
return parts[partIndex]
ipAddrCommaSeparated := strings.Join(ipAddrListCommaSeparated, ",")
ips := strings.Split(ipAddrCommaSeparated, ",")
for i, p := range ips {
ips[i] = strings.TrimSpace(p)
}
if lookup == "X-Real-IP" && realIP != "" {
return realIP
ipIndex := len(ips) - 1 - ipLookup.IndexFromRight
if ipIndex < 0 {
ipIndex = 0
}
return ips[ipIndex]
}
return ""
@@ -8,7 +8,7 @@ import (
cache "github.com/go-pkgz/expirable-cache/v3"
"github.com/didip/tollbooth/v7/internal/time/rate"
"github.com/didip/tollbooth/v8/internal/time/rate"
)
// New is a constructor for Limiter.
@@ -19,7 +19,6 @@ func New(generalExpirableOptions *ExpirableOptions) *Limiter {
SetMessage("You have reached maximum request limit.").
SetStatusCode(429).
SetOnLimitReached(nil).
SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
SetForwardedForIndexFromBehind(0).
SetHeaders(make(map[string][]string)).
SetContextValues(make(map[string][]string)).
@@ -43,6 +42,18 @@ func New(generalExpirableOptions *ExpirableOptions) *Limiter {
return lmt
}
// IPLookup is a config struct to define how users want to pick the remote IP address.
type IPLookup struct {
// The name of lookup method.
// Possible options are: RemoteAddr, X-Forwarded-For, X-Real-IP, CF-Connecting-IP
// All other headers are considered unknown and will be ignored.
Name string
// The index position to pick the ip address from a comma separated list.
// The index goes from right to left.
IndexFromRight int
}
// Limiter is a config struct to limit a particular request handler.
type Limiter struct {
// Maximum number of requests to limit per second.
@@ -66,10 +77,9 @@ type Limiter struct {
// An option to write back what you want upon reaching a limit.
overrideDefaultResponseWriter bool
// List of places to look up IP address.
// Default is "RemoteAddr", "X-Forwarded-For", "X-Real-IP".
// You can rearrange the order as you like.
ipLookups []string
// Explicitly define how to look up IP address.
// This is intended to replace ipLookups
explicitIPLookup IPLookup
forwardedForIndex int
@@ -270,10 +280,12 @@ func (l *Limiter) ExecOnLimitReached(w http.ResponseWriter, r *http.Request) {
}
// SetOverrideDefaultResponseWriter is a thread-safe way of setting the response writer override variable.
func (l *Limiter) SetOverrideDefaultResponseWriter(override bool) {
func (l *Limiter) SetOverrideDefaultResponseWriter(override bool) *Limiter {
l.Lock()
l.overrideDefaultResponseWriter = override
l.Unlock()
return l
}
// GetOverrideDefaultResponseWriter is a thread-safe way of getting the response writer override variable.
@@ -283,20 +295,22 @@ func (l *Limiter) GetOverrideDefaultResponseWriter() bool {
return l.overrideDefaultResponseWriter
}
// SetIPLookups is thread-safe way of setting list of places to look up IP address.
func (l *Limiter) SetIPLookups(ipLookups []string) *Limiter {
// SetIPLookup is thread-safe way of setting an explicit way to look up IP address.
// This method is intended to replace SetIPLookups (version 6 or older).
func (l *Limiter) SetIPLookup(lookup IPLookup) *Limiter {
l.Lock()
l.ipLookups = ipLookups
l.explicitIPLookup = lookup
l.Unlock()
return l
}
// GetIPLookups is thread-safe way of getting list of places to look up IP address.
func (l *Limiter) GetIPLookups() []string {
// GetIPLookup is thread-safe way of getting an explicit way to look up IP address.
// This method is intended to replace the old GetIPLookups (version 6 or older).
func (l *Limiter) GetIPLookup() IPLookup {
l.RLock()
defer l.RUnlock()
return l.ipLookups
return l.explicitIPLookup
}
// SetIgnoreURL is thread-safe way of setting whenever ignore the URL on rate limit keys
@@ -7,9 +7,9 @@ import (
"net/http"
"strings"
"github.com/didip/tollbooth/v7/errors"
"github.com/didip/tollbooth/v7/libstring"
"github.com/didip/tollbooth/v7/limiter"
"github.com/didip/tollbooth/v8/errors"
"github.com/didip/tollbooth/v8/libstring"
"github.com/didip/tollbooth/v8/limiter"
)
// setResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
@@ -37,8 +37,7 @@ func setRateLimitResponseHeaders(lmt *limiter.Limiter, w http.ResponseWriter, to
func NewLimiter(max float64, tbOptions *limiter.ExpirableOptions) *limiter.Limiter {
return limiter.New(tbOptions).
SetMax(max).
SetBurst(int(math.Max(1, max))).
SetIPLookups([]string{"X-Forwarded-For", "X-Real-IP", "RemoteAddr"})
SetBurst(int(math.Max(1, max)))
}
// LimitByKeys keeps track number of request made by keys separated by pipe.
@@ -63,7 +62,7 @@ func ShouldSkipLimiter(lmt *limiter.Limiter, r *http.Request) bool {
// ---------------------------------
// Filter by remote ip
// If we are unable to find remoteIP, skip limiter
remoteIP := libstring.RemoteIP(lmt.GetIPLookups(), lmt.GetForwardedForIndexFromBehind(), r)
remoteIP := libstring.RemoteIPFromIPLookup(lmt.GetIPLookup(), r)
remoteIP = libstring.CanonicalizeIP(remoteIP)
if remoteIP == "" {
return true
@@ -195,7 +194,7 @@ func ShouldSkipLimiter(lmt *limiter.Limiter, r *http.Request) bool {
// BuildKeys generates a slice of keys to rate-limit by given limiter and request structs.
func BuildKeys(lmt *limiter.Limiter, r *http.Request) [][]string {
remoteIP := libstring.RemoteIP(lmt.GetIPLookups(), lmt.GetForwardedForIndexFromBehind(), r)
remoteIP := libstring.RemoteIPFromIPLookup(lmt.GetIPLookup(), r)
remoteIP = libstring.CanonicalizeIP(remoteIP)
path := r.URL.Path
sliceKeys := make([][]string, 0)
@@ -347,3 +346,30 @@ func LimitHandler(lmt *limiter.Limiter, next http.Handler) http.Handler {
func LimitFuncHandler(lmt *limiter.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
return LimitHandler(lmt, http.HandlerFunc(nextFunc))
}
// HTTPMiddleware wraps http.Handler with tollbooth limiter
func HTTPMiddleware(lmt *limiter.Limiter) func(http.Handler) http.Handler {
// // set IP lookup only if not set
if lmt.GetIPLookup().Name == "" {
lmt.SetIPLookup(limiter.IPLookup{Name: "RemoteAddr"})
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
select {
case <-r.Context().Done():
http.Error(w, "Context was canceled", http.StatusServiceUnavailable)
return
default:
if httpError := LimitByRequest(lmt, w, r); httpError != nil {
lmt.ExecOnLimitReached(w, r)
w.Header().Add("Content-Type", lmt.GetMessageContentType())
w.WriteHeader(httpError.StatusCode)
w.Write([]byte(httpError.Message)) //nolint:gosec // not much we can do here with failed write
return
}
next.ServeHTTP(w, r)
}
})
}
}
-33
View File
@@ -1,33 +0,0 @@
## tollbooth_chi
[Chi](https://github.com/pressly/chi) middleware for rate limiting HTTP requests.
## Five Minutes Tutorial
```
package main
import (
"github.com/didip/tollbooth"
"github.com/didip/tollbooth_chi"
"github.com/pressly/chi"
"net/http"
"time"
)
func main() {
// Create a limiter struct.
limiter := tollbooth.NewLimiter(1, nil)
r := chi.NewRouter()
r.Use(tollbooth_chi.LimitHandler(limiter))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
})
http.ListenAndServe(":12345", r)
}
```
-45
View File
@@ -1,45 +0,0 @@
package tollbooth_chi
import (
"net/http"
"github.com/didip/tollbooth/v7"
"github.com/didip/tollbooth/v7/limiter"
)
func LimitHandler(lmt *limiter.Limiter) func(http.Handler) http.Handler {
return func(handler http.Handler) http.Handler {
wrapper := &limiterWrapper{
lmt: lmt,
}
wrapper.handler = handler
return wrapper
}
}
type limiterWrapper struct {
lmt *limiter.Limiter
handler http.Handler
}
func (l *limiterWrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
select {
case <-ctx.Done():
http.Error(w, "Context was canceled", http.StatusServiceUnavailable)
return
default:
httpError := tollbooth.LimitByRequest(l.lmt, w, r)
if httpError != nil {
l.lmt.ExecOnLimitReached(w, r)
w.Header().Add("Content-Type", l.lmt.GetMessageContentType())
w.WriteHeader(httpError.StatusCode)
w.Write([]byte(httpError.Message))
return
}
l.handler.ServeHTTP(w, r)
}
}