-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathparental_categories.go
More file actions
175 lines (154 loc) · 4.76 KB
/
parental_categories.go
File metadata and controls
175 lines (154 loc) · 4.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
File: parental_categories.go
Version: 2.0.0 (Split)
Updated: 2026-04-10
Description:
Category data management and lookups for the sdproxy parental subsystem.
Extracted parsing, tree consolidation, and data loading into:
- parental_loader.go
- parental_parser.go
- parental_consolidation.go
WHAT LIVES HERE:
categoryData / wildcardCatEntry — runtime lookup types.
catMap — atomic.Pointer holding the live category data.
catMinLabels / catMaxLabels — suffix-walk depth bounds.
isWildcard / matchGlob — wildcard pattern matching.
categoryOf — hot-path domain → category lookup.
*/
package main
import (
"net/netip"
"strings"
"sync/atomic"
)
// ---------------------------------------------------------------------------
// Category data structures
// ---------------------------------------------------------------------------
// wildcardCatEntry holds a single glob pattern from an add: override and the
// category it belongs to. Stored in categoryData.patterns.
// Patterns are pre-lowercased; matching uses matchGlob().
type wildcardCatEntry struct {
pattern string // glob pattern (contains '*' and/or '?')
cat string // category name
}
// cidrCatEntry holds a parsed CIDR network and its mapped category.
type cidrCatEntry struct {
prefix netip.Prefix
cat string
}
// categoryData is the runtime lookup table for domain categorisation.
// Stored in catMap via atomic.Pointer for lock-free hot-path reads.
type categoryData struct {
// apex maps lowercased apex domains (no trailing dot) to category names.
// Populated from source entries and plain (non-wildcard) add: overrides.
// Matched by suffix walk in categoryOf().
apex map[string]string
// patterns holds wildcard add: entries in insertion order.
// Each entry is tested with matchGlob() against the full queried domain —
// no suffix walk. Patterns are evaluated BEFORE the apex map so that
// manual wildcard overrides always beat source entries.
patterns []wildcardCatEntry
// ips maps singular IP addresses to category names.
ips map[netip.Addr]string
// cidrs maps subnet blocks to category names.
cidrs []cidrCatEntry
}
// catMap holds the current categoryData. Replaced atomically by
// loadAllCategoryLists; read lock-free by categoryOf on the hot path.
var catMap atomic.Pointer[categoryData]
// catMinLabels and catMaxLabels bound the suffix walk in categoryOf().
// Computed by computeCatLabelBounds() after every apex-map build.
// Defaults are conservative: min=1 (no floor), max=128 (no ceiling until load).
var (
catMinLabels = 1
catMaxLabels = 128
)
// ---------------------------------------------------------------------------
// Wildcard helpers
// ---------------------------------------------------------------------------
// isWildcard reports whether s contains any wildcard characters ('*' or '?').
func isWildcard(s string) bool {
return strings.ContainsAny(s, "*?")
}
// matchGlob reports whether s fully matches the glob pattern p.
func matchGlob(p, s string) bool {
starP, starS := -1, 0
pi, si := 0, 0
for si < len(s) {
switch {
case pi < len(p) && p[pi] == '*':
starP = pi
starS = si
pi++
case pi < len(p) && (p[pi] == '?' || p[pi] == s[si]):
pi++
si++
case starP >= 0:
starS++
si = starS
pi = starP + 1
default:
return false
}
}
for pi < len(p) && p[pi] == '*' {
pi++
}
return pi == len(p)
}
// ---------------------------------------------------------------------------
// Category lookup (hot path)
// ---------------------------------------------------------------------------
// categoryOf returns the matched category name and the exact apex/wildcard/IP
// pattern that triggered the match. Both are returned so the PARENTAL LOG
// subsystem can output explicit reasoning.
func categoryOf(qname string) (string, string) {
data := catMap.Load()
if data == nil {
return "", ""
}
// 1. Directly short circuit for IP lookups (Filter IPs Support)
if ip, err := netip.ParseAddr(qname); err == nil {
ip = ip.Unmap()
if cat, ok := data.ips[ip]; ok {
return cat, ip.String()
}
for _, c := range data.cidrs {
if c.prefix.Contains(ip) {
return c.cat, c.prefix.String()
}
}
return "", ""
}
for _, e := range data.patterns {
if matchGlob(e.pattern, qname) {
return e.cat, e.pattern
}
}
labels := countDomainLabels(qname)
ceiling := catMaxLabels
if ceiling > labels {
ceiling = labels
}
search := qname
for labels > ceiling {
idx := strings.IndexByte(search, '.')
if idx < 0 {
break
}
search = search[idx+1:]
labels--
}
for {
if cat, ok := data.apex[search]; ok {
return cat, search
}
idx := strings.IndexByte(search, '.')
if idx < 0 || labels <= catMinLabels {
break
}
search = search[idx+1:]
labels--
}
return "", ""
}