Rewrite cname uncloaking code to account for new ipaddress= option · gorhill/uBlock@6acf97b (original) (raw)
`@@ -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
`}
`