-
Notifications
You must be signed in to change notification settings - Fork 18
/
request.go
206 lines (168 loc) · 5.09 KB
/
request.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package rules
import (
"net/netip"
"strings"
"github.com/AdguardTeam/urlfilter/filterutil"
"golang.org/x/net/publicsuffix"
)
// maxURLLength limits the URL length by 4 KiB. It appears that there
// can be URLs longer than a megabyte, and it makes no sense to go
// through the whole URL.
const maxURLLength = 4 * 1024
// RequestType is the request types enumeration
type RequestType uint32
const (
// TypeDocument (main frame)
TypeDocument RequestType = 1 << iota
// TypeSubdocument (iframe) $subdocument
TypeSubdocument
// TypeScript (javascript, etc) $script
TypeScript
// TypeStylesheet (css) $stylesheet
TypeStylesheet
// TypeObject (flash, etc) $object
TypeObject
// TypeImage (any image) $image
TypeImage
// TypeXmlhttprequest (ajax/fetch) $xmlhttprequest
TypeXmlhttprequest
// TypeMedia (video/music) $media
TypeMedia
// TypeFont (any custom font) $font
TypeFont
// TypeWebsocket (a websocket connection) $websocket
TypeWebsocket
// TypePing (navigator.sendBeacon() or ping attribute on links) $ping
TypePing
// TypeOther - any other request type
TypeOther
)
// Count returns count of the enabled flags
func (t RequestType) Count() int {
if t == 0 {
return 0
}
flags := uint32(t)
count := 0
var i uint
for i = 0; i < 32; i++ {
mask := uint32(1 << i)
if (flags & mask) == mask {
count++
}
}
return count
}
// Request represents a web filtering request with all it's necessary
// properties.
type Request struct {
// ClientIP is the IP address to match against $client modifiers. The
// default zero value won't be considered.
ClientIP netip.Addr
// ClientName is the name to match against $client modifiers. The default
// empty value won't be considered.
ClientName string
// URL is the full request URL.
URL string
// URLLowerCase is the full request URL in lower case.
URLLowerCase string
// Hostname is the hostname to filter.
Hostname string
// Domain is the effective top-level domain of the request with an
// additional label.
Domain string
// SourceURL is the full URL of the source.
SourceURL string
// SourceHostname is the hostname of the source.
SourceHostname string
// SourceDomain is the effective top-level domain of the source with an
// additional label.
SourceDomain string
// SortedClientTags is the list of tags to match against $ctag modifiers.
SortedClientTags []string
// RequestType is the type of the filtering request.
RequestType RequestType
// DNSType is the type of the resource record (RR) of a DNS request, for
// example "A" or "AAAA". See [RRValue] for all acceptable constants and
// their corresponding values.
DNSType uint16
// ThirdParty is true if the filtering request should consider $third-party
// modifier.
ThirdParty bool
// IsHostnameRequest means that the request is for a given Hostname, and not
// for a URL, and we don't really know what protocol it is. This can be
// true for DNS requests, for HTTP CONNECT, or for SNI matching.
IsHostnameRequest bool
}
// NewRequest creates a new instance of "Request" and populates it's fields
func NewRequest(url, sourceURL string, requestType RequestType) *Request {
if len(url) > maxURLLength {
url = url[:maxURLLength]
}
if len(sourceURL) > maxURLLength {
sourceURL = sourceURL[:maxURLLength]
}
r := Request{
RequestType: requestType,
URL: url,
URLLowerCase: strings.ToLower(url),
Hostname: filterutil.ExtractHostname(url),
SourceURL: sourceURL,
SourceHostname: filterutil.ExtractHostname(sourceURL),
}
domain := effectiveTLDPlusOne(r.Hostname)
if domain != "" {
r.Domain = domain
} else {
r.Domain = r.Hostname
}
sourceDomain := effectiveTLDPlusOne(r.SourceHostname)
if sourceDomain != "" {
r.SourceDomain = sourceDomain
} else {
r.SourceDomain = r.SourceHostname
}
if r.SourceDomain != "" && r.SourceDomain != r.Domain {
r.ThirdParty = true
}
return &r
}
// NewRequestForHostname creates a new instance of "Request" for matching a
// hostname. It uses "http://" as a protocol and TypeDocument as a request
// type.
func NewRequestForHostname(hostname string) (r *Request) {
// Do not use fmt.Sprintf or url.URL to achieve better performance.
// Hostname validation should be performed by the function caller.
urlStr := "http://" + hostname
r = &Request{
RequestType: TypeDocument,
URL: urlStr,
URLLowerCase: urlStr,
Hostname: hostname,
ThirdParty: false,
IsHostnameRequest: true,
}
if domain := effectiveTLDPlusOne(r.Hostname); domain != "" {
r.Domain = domain
} else {
r.Domain = r.Hostname
}
return r
}
// effectiveTLDPlusOne is a faster version of publicsuffix.EffectiveTLDPlusOne
// that avoids using fmt.Errorf when the domain is less or equal the suffix.
func effectiveTLDPlusOne(hostname string) (domain string) {
hostnameLen := len(hostname)
if hostnameLen < 1 {
return ""
}
if hostname[0] == '.' || hostname[hostnameLen-1] == '.' {
return ""
}
suffix, _ := publicsuffix.PublicSuffix(hostname)
i := hostnameLen - len(suffix) - 1
if i < 0 || hostname[i] != '.' {
return ""
}
return hostname[1+strings.LastIndex(hostname[:i], "."):]
}