@@ -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
}
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4