adguardhome/rewrite_rules.go
Rama main · réplica de lectura del repositorio original.
package adguardhome
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"sigs.k8s.io/external-dns/endpoint"
)
func rewriteRuleFormat() ruleFormat {
return ruleFormat{
formatRules: formatRewriteRules,
needsMigration: func(rule string) bool {
return !strings.HasPrefix(rule, "! ")
},
}
}
func formatRewriteRules(endpoints []*endpoint.Endpoint, suffix string) ([]string, error) {
rules := make([]string, 0, 2*len(endpoints))
for _, e := range endpoints {
if strings.Contains(e.DNSName, "*") {
return nil, fmt.Errorf("wildcard hostname %q is not supported in rewrite mode", e.DNSName)
}
rewrite, err := dnsRewriteRule(e)
if err != nil {
return nil, fmt.Errorf("invalid %s record for %s: %w", e.RecordType, e.DNSName, err)
}
// Adblock-style rules cannot carry inline ownership comments.
metadata := "! " + endpointToString(e, suffix)
if e.RecordType == endpoint.RecordTypeTXT {
data, err := json.Marshal(e)
if err != nil {
return nil, fmt.Errorf("encode TXT metadata: %w", err)
}
metadata = fmt.Sprintf("! %s %s", suffix, data)
}
rules = append(rules, metadata, rewrite)
}
return rules, nil
}
func dnsRewriteRule(e *endpoint.Endpoint) (string, error) {
value := e.Targets[0]
if e.RecordType == endpoint.RecordTypeTXT {
var err error
value, err = txtRewriteValue(value)
if err != nil {
return "", err
}
return fmt.Sprintf("|%s|$dnstype=TXT,dnsrewrite=NOERROR;TXT;%s", e.DNSName, value), nil
}
return fmt.Sprintf("|%s|$dnsrewrite=NOERROR;%s;%s", e.DNSName, e.RecordType, value), nil
}
func txtRewriteValue(target string) (string, error) {
if strings.ContainsAny(target, "\r\n") {
return "", fmt.Errorf("TXT target must not contain literal line breaks")
}
value := target
if strings.HasPrefix(target, `"`) {
if len(target) < 2 || !strings.HasSuffix(target, `"`) {
return "", fmt.Errorf("unterminated quoted TXT target")
}
var decoded strings.Builder
quoted := target[1 : len(target)-1]
for i := 0; i < len(quoted); i++ {
b := quoted[i]
switch b {
case '"':
return "", fmt.Errorf("TXT target must contain a single quoted string")
case '\\':
i++
if i == len(quoted) {
return "", fmt.Errorf("incomplete TXT escape")
}
b = quoted[i]
if i+2 < len(quoted) && b >= '0' && b <= '9' && quoted[i+1] >= '0' && quoted[i+1] <= '9' && quoted[i+2] >= '0' && quoted[i+2] <= '9' {
n, err := strconv.ParseUint(quoted[i:i+3], 10, 8)
if err != nil {
return "", fmt.Errorf("invalid DNS TXT escape: %w", err)
}
b = byte(n)
i += 2
}
}
decoded.WriteByte(b)
}
value = decoded.String()
}
// AdGuard's rewrite path emits one DNS character-string per TXT value.
if len(value) > 255 {
return "", fmt.Errorf("TXT value exceeds 255 bytes")
}
var escaped strings.Builder
for i := 0; i < len(value); i++ {
b := value[i]
if b <= ' ' || b >= 127 || strings.ContainsRune(",$#\"\\", rune(b)) {
// Decimal DNS escapes survive rule parsing and avoid modifier injection.
fmt.Fprintf(&escaped, "\\%03d", b)
} else {
escaped.WriteByte(b)
}
}
return escaped.String(), nil
}