mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/1Panel-dev/1Panel.git
synced 2026-09-21 00:24:12 +08:00
88 lines
2.4 KiB
Go
88 lines
2.4 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/gin-gonic/gin"
|
|
"golang.org/x/time/rate"
|
|
)
|
|
|
|
var (
|
|
fileShareCodePattern = regexp.MustCompile(`^[A-Za-z0-9]{10,16}$`)
|
|
fileShareLimiterCleanupLock sync.Mutex
|
|
fileShareLimiterCleanupAt time.Time
|
|
publicIPLimiters sync.Map
|
|
publicCodeLimiters sync.Map
|
|
)
|
|
|
|
type visitorLimiter struct {
|
|
limiter *rate.Limiter
|
|
lastSeen time.Time
|
|
}
|
|
|
|
func FileSharePublicAccess() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
code := strings.TrimSpace(c.Query("code"))
|
|
|
|
if code != "" && !fileShareCodePattern.MatchString(code) {
|
|
helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrFileShareInvalid", buserr.New("ErrFileShareInvalid"))
|
|
return
|
|
}
|
|
|
|
ip := c.ClientIP()
|
|
if !allowLimiter(&publicIPLimiters, "ip:"+ip, rate.Every(time.Second), 20) {
|
|
helper.ErrorWithDetail(c, http.StatusTooManyRequests, "ErrFileShareRateLimit", buserr.New("ErrFileShareRateLimit"))
|
|
return
|
|
}
|
|
if code != "" && (strings.HasSuffix(c.Request.URL.Path, "/share/check") || strings.HasSuffix(c.Request.URL.Path, "/share/download")) {
|
|
if !allowLimiter(&publicCodeLimiters, "code:"+ip+":"+code, rate.Every(5*time.Second), 4) {
|
|
helper.ErrorWithDetail(c, http.StatusTooManyRequests, "ErrFileShareRateLimit", buserr.New("ErrFileShareRateLimit"))
|
|
return
|
|
}
|
|
}
|
|
|
|
maybeCleanupLimiters()
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func allowLimiter(store *sync.Map, key string, refill rate.Limit, burst int) bool {
|
|
now := time.Now()
|
|
value, _ := store.LoadOrStore(key, &visitorLimiter{
|
|
limiter: rate.NewLimiter(refill, burst),
|
|
lastSeen: now,
|
|
})
|
|
item := value.(*visitorLimiter)
|
|
item.lastSeen = now
|
|
return item.limiter.Allow()
|
|
}
|
|
|
|
func maybeCleanupLimiters() {
|
|
fileShareLimiterCleanupLock.Lock()
|
|
defer fileShareLimiterCleanupLock.Unlock()
|
|
|
|
now := time.Now()
|
|
if !fileShareLimiterCleanupAt.IsZero() && now.Sub(fileShareLimiterCleanupAt) < 10*time.Minute {
|
|
return
|
|
}
|
|
fileShareLimiterCleanupAt = now
|
|
cleanupLimiterMap(&publicIPLimiters, now)
|
|
cleanupLimiterMap(&publicCodeLimiters, now)
|
|
}
|
|
|
|
func cleanupLimiterMap(store *sync.Map, now time.Time) {
|
|
store.Range(func(key, value any) bool {
|
|
item, ok := value.(*visitorLimiter)
|
|
if ok && now.Sub(item.lastSeen) > 30*time.Minute {
|
|
store.Delete(key)
|
|
}
|
|
return true
|
|
})
|
|
}
|