128 lines
3.0 KiB
Go
128 lines
3.0 KiB
Go
package traefik_botfilter
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/netip"
|
|
"net/url"
|
|
pathpkg "path"
|
|
"strings"
|
|
)
|
|
|
|
func clientIP(r *http.Request, cfg *compiledConfig) netip.Addr {
|
|
remote := parseRemoteAddress(r.RemoteAddr)
|
|
if cfg.clientIPHeader == "" || !addressInPrefixes(remote, cfg.trustedProxies) {
|
|
return remote
|
|
}
|
|
|
|
// A trusted reverse proxy must overwrite (not append to an untrusted
|
|
// client-provided value) this header. The left-most valid value is the
|
|
// original client in the conventional X-Forwarded-For representation.
|
|
for _, value := range strings.Split(r.Header.Get(cfg.clientIPHeader), ",") {
|
|
if addr, err := netip.ParseAddr(strings.TrimSpace(value)); err == nil {
|
|
return addr.Unmap()
|
|
}
|
|
}
|
|
return remote
|
|
}
|
|
|
|
func parseRemoteAddress(value string) netip.Addr {
|
|
host, _, err := net.SplitHostPort(strings.TrimSpace(value))
|
|
if err == nil {
|
|
value = host
|
|
}
|
|
addr, err := netip.ParseAddr(strings.Trim(strings.TrimSpace(value), "[]"))
|
|
if err != nil {
|
|
return netip.Addr{}
|
|
}
|
|
return addr.Unmap()
|
|
}
|
|
|
|
func addressInPrefixes(addr netip.Addr, prefixes []netip.Prefix) bool {
|
|
if !addr.IsValid() {
|
|
return false
|
|
}
|
|
for _, prefix := range prefixes {
|
|
if prefix.Contains(addr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normaliseTokens(values []string) []string {
|
|
result := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
if token := strings.ToLower(strings.TrimSpace(value)); token != "" {
|
|
result = append(result, token)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalisePaths(values []string) []string {
|
|
result := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
if path := normalisePath(value); path != "" {
|
|
result = append(result, path)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normaliseExtensions(values []string) []string {
|
|
result := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
value = strings.ToLower(strings.TrimSpace(value))
|
|
if value == "" {
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(value, ".") {
|
|
value = "." + value
|
|
}
|
|
result = append(result, value)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func normalisePath(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return ""
|
|
}
|
|
value = strings.ReplaceAll(value, "\\", "/")
|
|
if !strings.HasPrefix(value, "/") {
|
|
value = "/" + value
|
|
}
|
|
return strings.ToLower(pathpkg.Clean(value))
|
|
}
|
|
|
|
func requestPaths(r *http.Request) []string {
|
|
values := []string{r.URL.Path, r.URL.EscapedPath()}
|
|
result := make([]string, 0, len(values)*3)
|
|
seen := make(map[string]struct{})
|
|
for _, value := range values {
|
|
for i := 0; i < 3 && value != ""; i++ {
|
|
normalised := normalisePath(value)
|
|
if _, ok := seen[normalised]; !ok {
|
|
seen[normalised] = struct{}{}
|
|
result = append(result, normalised)
|
|
}
|
|
decoded, err := url.PathUnescape(value)
|
|
if err != nil || decoded == value {
|
|
break
|
|
}
|
|
value = decoded
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func pathMatches(path, rule string) bool {
|
|
return path == rule || strings.HasPrefix(path, rule+"/")
|
|
}
|
|
|
|
func hasHeader(r *http.Request, name string) bool {
|
|
return strings.TrimSpace(r.Header.Get(name)) != ""
|
|
}
|