96 lines
2.4 KiB
Go
96 lines
2.4 KiB
Go
package store
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// getClientIP extracts the client IP address, checking X-Forwarded-For first
|
|
func getClientIP(r *http.Request) string {
|
|
// Check X-Forwarded-For header (comma-separated list, first is client)
|
|
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
|
// Take the first IP in the list
|
|
ips := strings.Split(xff, ",")
|
|
if len(ips) > 0 {
|
|
return strings.TrimSpace(ips[0])
|
|
}
|
|
}
|
|
|
|
// Fall back to RemoteAddr (format: "IP:port" or "[IPv6]:port")
|
|
// Use net.SplitHostPort to properly handle both IPv4 and IPv6
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
// If SplitHostPort fails, return as-is (shouldn't happen with valid RemoteAddr)
|
|
return r.RemoteAddr
|
|
}
|
|
return host
|
|
}
|
|
|
|
// TrackRedirect increments the redirect counter for this IP+UA+Path combination
|
|
// Returns the current attempt count, whether limit was exceeded, and the track details
|
|
func (s *Store) TrackRedirect(r *http.Request, path string, maxAttempts int) (attempts int, exceeded bool, track *RedirectTrack) {
|
|
if r == nil {
|
|
return 0, false, nil
|
|
}
|
|
|
|
ip := getClientIP(r)
|
|
userAgent := r.UserAgent()
|
|
key := redirectKey(ip, userAgent, path)
|
|
|
|
now := time.Now()
|
|
expiresAt := now.Add(5 * time.Minute)
|
|
|
|
// Try to load existing track
|
|
val, exists := s.redirectTracks.Load(key)
|
|
if exists {
|
|
track = val.(*RedirectTrack)
|
|
|
|
// Check if expired
|
|
if now.After(track.ExpiresAt) {
|
|
// Expired, start fresh
|
|
track = &RedirectTrack{
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
Path: path,
|
|
Attempts: 1,
|
|
FirstSeen: now,
|
|
ExpiresAt: expiresAt,
|
|
}
|
|
s.redirectTracks.Store(key, track)
|
|
return 1, false, track
|
|
}
|
|
|
|
// Increment existing
|
|
track.Attempts++
|
|
track.ExpiresAt = expiresAt // Extend expiry
|
|
exceeded = track.Attempts >= maxAttempts
|
|
return track.Attempts, exceeded, track
|
|
}
|
|
|
|
// Create new track
|
|
track = &RedirectTrack{
|
|
IP: ip,
|
|
UserAgent: userAgent,
|
|
Path: path,
|
|
Attempts: 1,
|
|
FirstSeen: now,
|
|
ExpiresAt: expiresAt,
|
|
}
|
|
s.redirectTracks.Store(key, track)
|
|
return 1, false, track
|
|
}
|
|
|
|
// ClearRedirectTrack removes a redirect tracking entry (called after successful completion)
|
|
func (s *Store) ClearRedirectTrack(r *http.Request, path string) {
|
|
if r == nil {
|
|
return
|
|
}
|
|
|
|
ip := getClientIP(r)
|
|
userAgent := r.UserAgent()
|
|
key := redirectKey(ip, userAgent, path)
|
|
s.redirectTracks.Delete(key)
|
|
}
|