-
Notifications
You must be signed in to change notification settings - Fork 21
/
cosmeticengine.go
190 lines (163 loc) · 5.19 KB
/
cosmeticengine.go
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
package urlfilter
import (
"github.com/AdguardTeam/urlfilter/filterlist"
"github.com/AdguardTeam/urlfilter/rules"
)
// CosmeticEngine combines all the cosmetic rules and allows to quickly
// find all rules matching this or that hostname
type CosmeticEngine struct {
lookupTables map[rules.CosmeticRuleType]*cosmeticLookupTable
}
// NewCosmeticEngine builds a new cosmetic engine from the specified rule storage
func NewCosmeticEngine(s *filterlist.RuleStorage) *CosmeticEngine {
engine := CosmeticEngine{
lookupTables: map[rules.CosmeticRuleType]*cosmeticLookupTable{
rules.CosmeticElementHiding: newCosmeticLookupTable(),
rules.CosmeticCSS: newCosmeticLookupTable(),
rules.CosmeticJS: newCosmeticLookupTable(),
},
}
scanner := s.NewRuleStorageScanner()
for scanner.Scan() {
f, _ := scanner.Rule()
rule, ok := f.(*rules.CosmeticRule)
if ok {
engine.addRule(rule)
}
}
return &engine
}
// addRule adds a new cosmetic rule to one of the lookup tables
func (e *CosmeticEngine) addRule(rule *rules.CosmeticRule) {
switch rule.Type {
case rules.CosmeticElementHiding:
e.lookupTables[rules.CosmeticElementHiding].addRule(rule)
default:
// TODO: Implement
// ignore
}
}
// StylesResult contains either element hiding or CSS rules
type StylesResult struct {
Generic []string `json:"generic"`
Specific []string `json:"specific"`
GenericExtCSS []string `json:"genericExtCss"`
SpecificExtCSS []string `json:"specificExtCss"`
}
func (s *StylesResult) append(r *rules.CosmeticRule) {
if r.IsGeneric() {
if r.ExtendedCSS {
s.GenericExtCSS = append(s.GenericExtCSS, r.Content)
} else {
s.Generic = append(s.Generic, r.Content)
}
} else {
if r.ExtendedCSS {
s.SpecificExtCSS = append(s.SpecificExtCSS, r.Content)
} else {
s.Specific = append(s.Specific, r.Content)
}
}
}
// ScriptsResult contains scripts to be executed on a page
type ScriptsResult struct {
Generic []string
Specific []string
}
// CosmeticResult represents all scripts and styles that needs to be injected into the page
type CosmeticResult struct {
ElementHiding StylesResult
CSS StylesResult
JS ScriptsResult
}
// Match builds scripts and styles that needs to be injected into the specified page
// hostname is the page hostname
// includeCSS defines if we should inject any CSS and element hiding rules (see $elemhide)
// includeJS defines if we should inject JS into the page (see $jsinject)
// includeGenericCSS defines if we should inject generic CSS and element hiding rules (see $generichide)
// TODO: Additionally, we should provide a method that writes result to an io.Writer
func (e *CosmeticEngine) Match(hostname string, includeCSS, includeJS, includeGenericCSS bool) CosmeticResult {
r := CosmeticResult{
ElementHiding: StylesResult{},
CSS: StylesResult{},
JS: ScriptsResult{},
}
if includeCSS {
c := e.lookupTables[rules.CosmeticElementHiding]
if includeGenericCSS {
for _, rule := range c.genericRules {
if !c.isWhitelisted(hostname, rule) && rule.Match(hostname) {
r.ElementHiding.append(rule)
}
}
}
rules := c.findByHostname(hostname)
if len(rules) > 0 {
for _, rule := range rules {
r.ElementHiding.append(rule)
}
}
}
// TODO: Implement CosmeticCSS and CosmeticJS
return r
}
// cosmeticLookupTable is a helper structure to speed up cosmetic rules matching
type cosmeticLookupTable struct {
byHostname map[string][]*rules.CosmeticRule // map with rules grouped by the permitted domains names
whitelist map[string][]*rules.CosmeticRule // map with whitelist rules. key is the rule content
genericRules []*rules.CosmeticRule // list of generic rules
}
// newCosmeticLookupTable creates a new empty instance of the lookup table
func newCosmeticLookupTable() *cosmeticLookupTable {
return &cosmeticLookupTable{
byHostname: map[string][]*rules.CosmeticRule{},
genericRules: []*rules.CosmeticRule{},
whitelist: map[string][]*rules.CosmeticRule{},
}
}
// addRule adds the specified rule to the lookup table
func (c *cosmeticLookupTable) addRule(f *rules.CosmeticRule) {
if f.Whitelist {
rules := c.whitelist[f.Content]
rules = append(rules, f)
c.whitelist[f.Content] = rules
return
}
if f.IsGeneric() {
c.genericRules = append(c.genericRules, f)
return
}
for _, hostname := range f.GetPermittedDomains() {
rules := c.byHostname[hostname]
rules = append(rules, f)
c.byHostname[hostname] = rules
}
}
// findByHostname looks for matching domain-specific rules
// Returns nil if nothing found
func (c *cosmeticLookupTable) findByHostname(hostname string) []*rules.CosmeticRule {
var rules []*rules.CosmeticRule
rulesByHostname, found := c.byHostname[hostname]
if !found {
return rules
}
for _, rule := range rulesByHostname {
if !c.isWhitelisted(hostname, rule) {
rules = append(rules, rule)
}
}
return rules
}
// isWhitelisted checks if this cosmetic rule is whitelisted on the specified hostname
func (c *cosmeticLookupTable) isWhitelisted(hostname string, f *rules.CosmeticRule) bool {
list, found := c.whitelist[f.Content]
if !found {
return false
}
for _, rule := range list {
if rule.Match(hostname) {
return true
}
}
return false
}