mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-08-02 00:48:33 -04:00
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package ban
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Weights maps a matcher key to a strike weight for the abuse ban-feed (see ParseWeights, WeightFor).
|
|
type Weights map[string]int
|
|
|
|
// ParseWeights normalizes a list like ["42909:10","403:2","*:1"] into a Weights map. A key is an
|
|
// exact ntfy code, a family ("429*"), a bare HTTP status ("403" -> "403*"), or "*"; weights are ints
|
|
// >= 0 (0 = exempt). Malformed entries are rejected so misconfiguration fails at startup.
|
|
func ParseWeights(entries []string) (Weights, error) {
|
|
out := make(Weights, len(entries))
|
|
for _, entry := range entries {
|
|
key, weightStr, ok := strings.Cut(entry, ":")
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid ban-weight %q, want KEY:WEIGHT", entry)
|
|
}
|
|
weight, err := strconv.Atoi(strings.TrimSpace(weightStr))
|
|
if err != nil || weight < 0 {
|
|
return nil, fmt.Errorf("invalid ban-weight value in %q, want a non-negative integer", entry)
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if !validWeightKey(key) {
|
|
return nil, fmt.Errorf("invalid ban-weight key in %q, want %q, an ntfy code, an HTTP status, or a PREFIX*", entry, "*")
|
|
}
|
|
// A bare 3-digit HTTP status is shorthand for the whole family (e.g. "403" -> "403*").
|
|
if len(key) == 3 && isAllDigits(key) {
|
|
key += "*"
|
|
}
|
|
out[key] = weight
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// WeightFor returns the strike weight for an ntfy error code, longest-match-wins (exact > family > "*").
|
|
// If nothing matches (no "*" catch-all) it returns the implied default 1, so a forgotten "*" still
|
|
// bans; use "*:0" to exempt everything not explicitly weighted.
|
|
func (w Weights) WeightFor(errorCode int) int {
|
|
code := strconv.Itoa(errorCode)
|
|
weight, bestLen, matched := 0, -1, false
|
|
for key, wt := range w {
|
|
matchLen := -1
|
|
switch {
|
|
case key == "*":
|
|
matchLen = 0
|
|
case strings.HasSuffix(key, "*"):
|
|
if prefix := strings.TrimSuffix(key, "*"); strings.HasPrefix(code, prefix) {
|
|
matchLen = len(prefix)
|
|
}
|
|
case key == code:
|
|
matchLen = len(code)
|
|
}
|
|
if matchLen > bestLen {
|
|
weight, bestLen, matched = wt, matchLen, true
|
|
}
|
|
}
|
|
if !matched {
|
|
return 1
|
|
}
|
|
return weight
|
|
}
|
|
|
|
// validWeightKey reports whether key is a legal matcher: "*", an all-digits code, or DIGITS*.
|
|
func validWeightKey(key string) bool {
|
|
if key == "*" {
|
|
return true
|
|
}
|
|
digits := strings.TrimSuffix(key, "*")
|
|
return digits != "" && isAllDigits(digits)
|
|
}
|
|
|
|
func isAllDigits(s string) bool {
|
|
for _, r := range s {
|
|
if r < '0' || r > '9' {
|
|
return false
|
|
}
|
|
}
|
|
return s != ""
|
|
}
|