@@ -26,8 +26,10 @@ import {
2626
2727/******************************************************************************/
2828
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 + $ /
3133
3234// Related issues:
3335// - https://github.com/gorhill/uBlock/issues/1327
@@ -40,21 +42,24 @@ vAPI.Net = class extends vAPI.Net {
4042 constructor ( ) {
4143 super ( ) ;
4244 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 ;
4552 this . cnameIgnoreList = null ;
4653 this . cnameIgnore1stParty = true ;
4754 this . cnameIgnoreExceptions = true ;
4855 this . cnameIgnoreRootDocument = true ;
49- this . cnameMaxTTL = 120 ;
5056 this . cnameReplayFullURL = false ;
51- this . cnameFlushTime = Date . now ( ) + this . cnameMaxTTL * 60000 ;
5257 }
58+
5359 setOptions ( options ) {
5460 super . setOptions ( options ) ;
5561 if ( 'cnameUncloakEnabled' in options ) {
56- cnameUncloakEnabled =
57- this . canUncloakCnames &&
62+ this . cnameUncloakEnabled =
5863 options . cnameUncloakEnabled !== false ;
5964 }
6065 if ( 'cnameIgnoreList' in options ) {
@@ -73,15 +78,13 @@ vAPI.Net = class extends vAPI.Net {
7378 this . cnameIgnoreRootDocument =
7479 options . cnameIgnoreRootDocument !== false ;
7580 }
76- if ( 'cnameMaxTTL' in options ) {
77- this . cnameMaxTTL = options . cnameMaxTTL || 120 ;
78- }
7981 if ( 'cnameReplayFullURL' in options ) {
8082 this . cnameReplayFullURL = options . cnameReplayFullURL === true ;
8183 }
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 ( ) ;
8486 }
87+
8588 normalizeDetails ( details ) {
8689 const type = details . type ;
8790
@@ -104,6 +107,7 @@ vAPI.Net = class extends vAPI.Net {
104107 }
105108 }
106109 }
110+
107111 denormalizeTypes ( types ) {
108112 if ( types . length === 0 ) {
109113 return Array . from ( this . validTypes ) ;
@@ -122,75 +126,19 @@ vAPI.Net = class extends vAPI.Net {
122126 }
123127 return Array . from ( out ) ;
124128 }
129+
125130 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 ;
181135 }
136+
182137 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' ) {
189139 return null ;
190140 }
191- if ( list === '*' ) {
192- return / ^ ./ ;
193- }
141+ if ( list === '*' ) { return / ^ ./ ; }
194142 return new RegExp (
195143 '(?:^|\\.)(?:' +
196144 list . trim ( )
@@ -200,9 +148,14 @@ vAPI.Net = class extends vAPI.Net {
200148 ')$'
201149 ) ;
202150 }
151+
203152 onBeforeSuspendableRequest ( details ) {
153+ const hn = hostnameFromNetworkURL ( details . url ) ;
154+ const dnsEntry = this . dnsFromCache ( hn ) ;
155+ if ( dnsEntry ?. ip ) {
156+ details . ip = dnsEntry . ip ;
157+ }
204158 const r = super . onBeforeSuspendableRequest ( details ) ;
205- if ( cnameUncloakEnabled === false ) { return r ; }
206159 if ( r !== undefined ) {
207160 if (
208161 r . cancel === true ||
@@ -212,25 +165,128 @@ vAPI.Net = class extends vAPI.Net {
212165 return r ;
213166 }
214167 }
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+ }
219172 }
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 ;
231191 }
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 )
232246 ) ;
247+ return ( this . dnsList [ i ] = promise ) ;
233248 }
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+
234290 suspendOneRequest ( details ) {
235291 const pending = {
236292 details : Object . assign ( { } , details ) ,
@@ -243,6 +299,7 @@ vAPI.Net = class extends vAPI.Net {
243299 this . pendingRequests . push ( pending ) ;
244300 return pending . promise ;
245301 }
302+
246303 unsuspendAllRequests ( discard = false ) {
247304 const pendingRequests = this . pendingRequests ;
248305 this . pendingRequests = [ ] ;
@@ -254,6 +311,7 @@ vAPI.Net = class extends vAPI.Net {
254311 ) ;
255312 }
256313 }
314+
257315 static canSuspend ( ) {
258316 return true ;
259317 }
0 commit comments