-
Notifications
You must be signed in to change notification settings - Fork 18
/
rule.go
161 lines (132 loc) · 3.35 KB
/
rule.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
package rules
import (
"errors"
"fmt"
"strings"
"github.com/asaskevich/govalidator"
)
// RuleSyntaxError represents an error while parsing a filtering rule
type RuleSyntaxError struct {
msg string
ruleText string
}
func (e *RuleSyntaxError) Error() string {
return fmt.Sprintf("syntax error: %s, rule: %s", e.msg, e.ruleText)
}
var (
// ErrUnsupportedRule signals that this might be a valid rule type,
// but it is not yet supported by this library
ErrUnsupportedRule = errors.New("this type of rules is unsupported")
)
// Rule is a base interface for all filtering rules
type Rule interface {
// Text returns the original rule text
Text() string
// GetFilterListID returns ID of the filter list this rule belongs to
GetFilterListID() int
}
// NewRule creates a new filtering rule from the specified line
// It returns nil if the line is empty or if it is a comment
func NewRule(line string, filterListID int) (Rule, error) {
line = strings.TrimSpace(line)
if line == "" || isComment(line) {
return nil, nil
}
if isCosmetic(line) {
return NewCosmeticRule(line, filterListID)
}
f, err := NewHostRule(line, filterListID)
if err == nil {
return f, nil
}
return NewNetworkRule(line, filterListID)
}
// isComment checks if the line is a comment
func isComment(line string) bool {
if len(line) == 0 {
return false
}
if line[0] == '!' {
return true
}
if line[0] == '#' {
if len(line) == 1 {
return true
}
// Now we should check that this is not a cosmetic rule
for _, marker := range cosmeticRulesMarkers {
if startsAtIndexWith(line, 0, marker) {
return false
}
}
return true
}
return false
}
// loadDomains loads $domain modifier or cosmetic rules domains
// domains is the list of domains
// sep is the separator character. for network rules it is '|', for cosmetic it is ','.
func loadDomains(domains string, sep string) (permittedDomains []string, restrictedDomains []string, err error) {
if domains == "" {
err = errors.New("no domains specified")
return
}
list := strings.Split(domains, sep)
for i := 0; i < len(list); i++ {
d := list[i]
restricted := false
if strings.HasPrefix(d, "~") {
restricted = true
d = d[1:]
}
if !govalidator.IsDNSName(d) {
err = fmt.Errorf("invalid domain specified: %s", domains)
return
}
if restricted {
restrictedDomains = append(restrictedDomains, d)
} else {
permittedDomains = append(permittedDomains, d)
}
}
return
}
// Return TRUE if ctag value format is correct: a-z0-9_
func isValidCTag(s string) bool {
for _, ch := range s {
if !((ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9') ||
ch == '_') {
return false
}
}
return true
}
// loadCTags loads $ctag modifier
// value: value of $ctag
// sep: separator character; for network rules it is '|'
func loadCTags(value string, sep string) (permittedCTags []string, restrictedCTags []string, err error) {
if value == "" {
err = errors.New("value is empty")
return
}
list := strings.Split(value, sep)
for i := 0; i < len(list); i++ {
d := list[i]
restricted := false
if strings.HasPrefix(d, "~") {
restricted = true
d = d[1:]
}
if !isValidCTag(d) {
err = fmt.Errorf("invalid ctag specified: %s", value)
return
}
if restricted {
restrictedCTags = append(restrictedCTags, d)
} else {
permittedCTags = append(permittedCTags, d)
}
}
return
}