@@ -26,8 +26,10 @@ import {
26
26
27
27
/******************************************************************************/
28
28
29
- // Canonical name-uncloaking feature.
30
- let cnameUncloakEnabled = browser . dns instanceof Object ;
29
+ const dnsAPI = browser . dns ;
30
+
31
+ const isPromise = o => o instanceof Promise ;
32
+ const reIPv4 = / ^ \d + \. \d + \. \d + \. \d + $ /
31
33
32
34
// Related issues:
33
35
// - https://github.com/gorhill/uBlock/issues/1327
@@ -40,21 +42,24 @@ vAPI.Net = class extends vAPI.Net {
40
42
constructor ( ) {
41
43
super ( ) ;
42
44
this . pendingRequests = [ ] ;
43
- this . canUncloakCnames = browser . dns instanceof Object ;
44
- this . cnames = new Map ( [ [ '' , null ] ] ) ;
45
+ this . dnsList = [ ] ; // ring buffer
46
+ this . dnsWritePtr = 0 ; // next write pointer in ring buffer
47
+ this . dnsMaxCount = 256 ; // max size of ring buffer
48
+ this . dnsDict = new Map ( ) ; // hn to index in ring buffer
49
+ this . dnsEntryTTL = 60000 ; // delay after which an entry is obsolete
50
+ this . canUncloakCnames = true ;
51
+ this . cnameUncloakEnabled = true ;
45
52
this . cnameIgnoreList = null ;
46
53
this . cnameIgnore1stParty = true ;
47
54
this . cnameIgnoreExceptions = true ;
48
55
this . cnameIgnoreRootDocument = true ;
49
- this . cnameMaxTTL = 120 ;
50
56
this . cnameReplayFullURL = false ;
51
- this . cnameFlushTime = Date . now ( ) + this . cnameMaxTTL * 60000 ;
52
57
}
58
+
53
59
setOptions ( options ) {
54
60
super . setOptions ( options ) ;
55
61
if ( 'cnameUncloakEnabled' in options ) {
56
- cnameUncloakEnabled =
57
- this . canUncloakCnames &&
62
+ this . cnameUncloakEnabled =
58
63
options . cnameUncloakEnabled !== false ;
59
64
}
60
65
if ( 'cnameIgnoreList' in options ) {
@@ -73,15 +78,13 @@ vAPI.Net = class extends vAPI.Net {
73
78
this . cnameIgnoreRootDocument =
74
79
options . cnameIgnoreRootDocument !== false ;
75
80
}
76
- if ( 'cnameMaxTTL' in options ) {
77
- this . cnameMaxTTL = options . cnameMaxTTL || 120 ;
78
- }
79
81
if ( 'cnameReplayFullURL' in options ) {
80
82
this . cnameReplayFullURL = options . cnameReplayFullURL === true ;
81
83
}
82
- this . cnames . clear ( ) ; this . cnames . set ( '' , null ) ;
83
- this . cnameFlushTime = Date . now ( ) + this . cnameMaxTTL * 60000 ;
84
+ this . dnsList . fill ( null ) ;
85
+ this . dnsDict . clear ( ) ;
84
86
}
87
+
85
88
normalizeDetails ( details ) {
86
89
const type = details . type ;
87
90
@@ -104,6 +107,7 @@ vAPI.Net = class extends vAPI.Net {
104
107
}
105
108
}
106
109
}
110
+
107
111
denormalizeTypes ( types ) {
108
112
if ( types . length === 0 ) {
109
113
return Array . from ( this . validTypes ) ;
@@ -122,75 +126,19 @@ vAPI.Net = class extends vAPI.Net {
122
126
}
123
127
return Array . from ( out ) ;
124
128
}
129
+
125
130
canonicalNameFromHostname ( hn ) {
126
- const cnRecord = this . cnames . get ( hn ) ;
127
- if ( cnRecord !== undefined && cnRecord !== null ) {
128
- return cnRecord . cname ;
129
- }
130
- }
131
- processCanonicalName ( hn , cnRecord , details ) {
132
- if ( cnRecord === null ) { return ; }
133
- if ( cnRecord . isRootDocument ) { return ; }
134
- const hnBeg = details . url . indexOf ( hn ) ;
135
- if ( hnBeg === - 1 ) { return ; }
136
- const oldURL = details . url ;
137
- let newURL = oldURL . slice ( 0 , hnBeg ) + cnRecord . cname ;
138
- const hnEnd = hnBeg + hn . length ;
139
- if ( this . cnameReplayFullURL ) {
140
- newURL += oldURL . slice ( hnEnd ) ;
141
- } else {
142
- const pathBeg = oldURL . indexOf ( '/' , hnEnd ) ;
143
- if ( pathBeg !== - 1 ) {
144
- newURL += oldURL . slice ( hnEnd , pathBeg + 1 ) ;
145
- }
146
- }
147
- details . url = newURL ;
148
- details . aliasURL = oldURL ;
149
- return super . onBeforeSuspendableRequest ( details ) ;
150
- }
151
- recordCanonicalName ( hn , record , isRootDocument ) {
152
- if ( ( this . cnames . size & 0b111111 ) === 0 ) {
153
- const now = Date . now ( ) ;
154
- if ( now >= this . cnameFlushTime ) {
155
- this . cnames . clear ( ) ; this . cnames . set ( '' , null ) ;
156
- this . cnameFlushTime = now + this . cnameMaxTTL * 60000 ;
157
- }
158
- }
159
- let cname =
160
- typeof record . canonicalName === 'string' &&
161
- record . canonicalName !== hn
162
- ? record . canonicalName
163
- : '' ;
164
- if (
165
- cname !== '' &&
166
- this . cnameIgnore1stParty &&
167
- domainFromHostname ( cname ) === domainFromHostname ( hn )
168
- ) {
169
- cname = '' ;
170
- }
171
- if (
172
- cname !== '' &&
173
- this . cnameIgnoreList !== null &&
174
- this . cnameIgnoreList . test ( cname )
175
- ) {
176
- cname = '' ;
177
- }
178
- const cnRecord = cname !== '' ? { cname, isRootDocument } : null ;
179
- this . cnames . set ( hn , cnRecord ) ;
180
- return cnRecord ;
131
+ if ( hn === '' ) { return ; }
132
+ const dnsEntry = this . dnsFromCache ( hn ) ;
133
+ if ( isPromise ( dnsEntry ) ) { return ; }
134
+ return dnsEntry ?. cname ;
181
135
}
136
+
182
137
regexFromStrList ( list ) {
183
- if (
184
- typeof list !== 'string' ||
185
- list . length === 0 ||
186
- list === 'unset' ||
187
- browser . dns instanceof Object === false
188
- ) {
138
+ if ( typeof list !== 'string' || list . length === 0 || list === 'unset' ) {
189
139
return null ;
190
140
}
191
- if ( list === '*' ) {
192
- return / ^ ./ ;
193
- }
141
+ if ( list === '*' ) { return / ^ ./ ; }
194
142
return new RegExp (
195
143
'(?:^|\\.)(?:' +
196
144
list . trim ( )
@@ -200,9 +148,14 @@ vAPI.Net = class extends vAPI.Net {
200
148
')$'
201
149
) ;
202
150
}
151
+
203
152
onBeforeSuspendableRequest ( details ) {
153
+ const hn = hostnameFromNetworkURL ( details . url ) ;
154
+ const dnsEntry = this . dnsFromCache ( hn ) ;
155
+ if ( dnsEntry ?. ip ) {
156
+ details . ip = dnsEntry . ip ;
157
+ }
204
158
const r = super . onBeforeSuspendableRequest ( details ) ;
205
- if ( cnameUncloakEnabled === false ) { return r ; }
206
159
if ( r !== undefined ) {
207
160
if (
208
161
r . cancel === true ||
@@ -212,25 +165,128 @@ vAPI.Net = class extends vAPI.Net {
212
165
return r ;
213
166
}
214
167
}
215
- const hn = hostnameFromNetworkURL ( details . url ) ;
216
- const cnRecord = this . cnames . get ( hn ) ;
217
- if ( cnRecord !== undefined ) {
218
- return this . processCanonicalName ( hn , cnRecord , details ) ;
168
+ if ( dnsEntry !== undefined ) {
169
+ if ( isPromise ( dnsEntry ) === false ) {
170
+ return this . onAfterDNSResolution ( hn , details , dnsEntry ) ;
171
+ }
219
172
}
220
- if ( details . proxyInfo && details . proxyInfo . proxyDNS ) { return ; }
221
- const documentUrl = details . documentUrl || details . url ;
222
- const isRootDocument = this . cnameIgnoreRootDocument &&
223
- hn === hostnameFromNetworkURL ( documentUrl ) ;
224
- return browser . dns . resolve ( hn , [ 'canonical_name' ] ) . then (
225
- rec => {
226
- const cnRecord = this . recordCanonicalName ( hn , rec , isRootDocument ) ;
227
- return this . processCanonicalName ( hn , cnRecord , details ) ;
228
- } ,
229
- ( ) => {
230
- this . cnames . set ( hn , null ) ;
173
+ if ( this . dnsShouldResolve ( hn ) === false ) { return ; }
174
+ if ( details . proxyInfo ?. proxyDNS ) { return ; }
175
+ const promise = dnsEntry || this . dnsResolve ( hn , details ) ;
176
+ return promise . then ( ( ) => this . onAfterDNSResolution ( hn , details ) ) ;
177
+ }
178
+
179
+ onAfterDNSResolution ( hn , details , dnsEntry ) {
180
+ if ( dnsEntry === undefined ) {
181
+ dnsEntry = this . dnsFromCache ( hn ) ;
182
+ if ( dnsEntry === undefined || isPromise ( dnsEntry ) ) { return ; }
183
+ }
184
+ let proceed = false ;
185
+ if ( dnsEntry . cname && this . cnameUncloakEnabled ) {
186
+ const newURL = this . uncloakURL ( hn , dnsEntry , details ) ;
187
+ if ( newURL ) {
188
+ details . aliasURL = details . url ;
189
+ details . url = newURL ;
190
+ proceed = true ;
231
191
}
192
+ }
193
+ if ( dnsEntry . ip && details . ip !== dnsEntry . ip ) {
194
+ details . ip = dnsEntry . ip
195
+ proceed = true ;
196
+ }
197
+ if ( proceed === false ) { return ; }
198
+ // Must call method on base class
199
+ return super . onBeforeSuspendableRequest ( details ) ;
200
+ }
201
+
202
+ dnsToCache ( hn , record , details ) {
203
+ const i = this . dnsDict . get ( hn ) ;
204
+ if ( i === undefined ) { return ; }
205
+ const dnsEntry = {
206
+ hn,
207
+ until : Date . now ( ) + this . dnsEntryTTL ,
208
+ } ;
209
+ if ( record ) {
210
+ const cname = this . cnameFromRecord ( hn , record , details ) ;
211
+ if ( cname ) { dnsEntry . cname = cname ; }
212
+ const ip = this . ipFromRecord ( record ) ;
213
+ if ( ip ) { dnsEntry . ip = ip ; }
214
+ }
215
+ this . dnsList [ i ] = dnsEntry ;
216
+ return dnsEntry ;
217
+ }
218
+
219
+ dnsFromCache ( hn ) {
220
+ const i = this . dnsDict . get ( hn ) ;
221
+ if ( i === undefined ) { return ; }
222
+ const dnsEntry = this . dnsList [ i ] ;
223
+ if ( dnsEntry === null ) { return ; }
224
+ if ( isPromise ( dnsEntry ) ) { return dnsEntry ; }
225
+ if ( dnsEntry . hn !== hn ) { return ; }
226
+ if ( dnsEntry . until >= Date . now ( ) ) { return dnsEntry ; }
227
+ this . dnsList [ i ] = null ;
228
+ this . dnsDict . delete ( hn )
229
+ }
230
+
231
+ dnsShouldResolve ( hn ) {
232
+ if ( hn === '' ) { return false ; }
233
+ const c0 = hn . charCodeAt ( 0 ) ;
234
+ if ( c0 === 0x5B /* [ */ ) { return false ; }
235
+ if ( c0 > 0x39 /* 9 */ ) { return true ; }
236
+ return reIPv4 . test ( hn ) === false ;
237
+ }
238
+
239
+ dnsResolve ( hn , details ) {
240
+ const i = this . dnsWritePtr ++ ;
241
+ this . dnsWritePtr %= this . dnsMaxCount ;
242
+ this . dnsDict . set ( hn , i ) ;
243
+ const promise = dnsAPI . resolve ( hn , [ 'canonical_name' ] ) . then (
244
+ rec => this . dnsToCache ( hn , rec , details ) ,
245
+ ( ) => this . dnsToCache ( hn )
232
246
) ;
247
+ return ( this . dnsList [ i ] = promise ) ;
233
248
}
249
+
250
+ cnameFromRecord ( hn , record , details ) {
251
+ const cn = record . canonicalName ;
252
+ if ( cn === undefined ) { return ; }
253
+ if ( cn === hn ) { return ; }
254
+ if ( this . cnameIgnore1stParty ) {
255
+ if ( domainFromHostname ( cn ) === domainFromHostname ( hn ) ) { return ; }
256
+ }
257
+ if ( this . cnameIgnoreList !== null ) {
258
+ if ( this . cnameIgnoreList . test ( cn ) === false ) { return ; }
259
+ }
260
+ if ( this . cnameIgnoreRootDocument ) {
261
+ const origin = hostnameFromNetworkURL ( details . documentUrl || details . url ) ;
262
+ if ( hn === origin ) { return ; }
263
+ }
264
+ return cn ;
265
+ }
266
+
267
+ uncloakURL ( hn , dnsEntry , details ) {
268
+ const hnBeg = details . url . indexOf ( hn ) ;
269
+ if ( hnBeg === - 1 ) { return ; }
270
+ const oldURL = details . url ;
271
+ const newURL = oldURL . slice ( 0 , hnBeg ) + dnsEntry . cname ;
272
+ const hnEnd = hnBeg + hn . length ;
273
+ if ( this . cnameReplayFullURL ) {
274
+ return newURL + oldURL . slice ( hnEnd ) ;
275
+ }
276
+ const pathBeg = oldURL . indexOf ( '/' , hnEnd ) ;
277
+ if ( pathBeg !== - 1 ) {
278
+ return newURL + oldURL . slice ( hnEnd , pathBeg + 1 ) ;
279
+ }
280
+ return newURL ;
281
+ }
282
+
283
+ ipFromRecord ( record ) {
284
+ const { addresses } = record ;
285
+ if ( Array . isArray ( addresses ) === false ) { return ; }
286
+ if ( addresses . length === 0 ) { return ; }
287
+ return addresses [ 0 ] ;
288
+ }
289
+
234
290
suspendOneRequest ( details ) {
235
291
const pending = {
236
292
details : Object . assign ( { } , details ) ,
@@ -243,6 +299,7 @@ vAPI.Net = class extends vAPI.Net {
243
299
this . pendingRequests . push ( pending ) ;
244
300
return pending . promise ;
245
301
}
302
+
246
303
unsuspendAllRequests ( discard = false ) {
247
304
const pendingRequests = this . pendingRequests ;
248
305
this . pendingRequests = [ ] ;
@@ -254,6 +311,7 @@ vAPI.Net = class extends vAPI.Net {
254
311
) ;
255
312
}
256
313
}
314
+
257
315
static canSuspend ( ) {
258
316
return true ;
259
317
}
0 commit comments