@browserless.io/browserless 2.23.0 → 2.24.0-beta-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/build/routes/chrome/http/pdf.post.body.json +8 -8
  2. package/build/routes/chromium/http/content.post.body.json +8 -8
  3. package/build/routes/chromium/http/pdf.post.body.json +8 -8
  4. package/build/routes/chromium/http/scrape.post.body.json +8 -8
  5. package/build/routes/chromium/http/screenshot.post.body.json +8 -8
  6. package/docker/base/Dockerfile +1 -1
  7. package/docker/multi/Dockerfile +2 -1
  8. package/docker/webkit/Dockerfile +2 -1
  9. package/extensions/ublock/_locales/ar/messages.json +5 -1
  10. package/extensions/ublock/_locales/az/messages.json +4 -0
  11. package/extensions/ublock/_locales/be/messages.json +5 -1
  12. package/extensions/ublock/_locales/bg/messages.json +4 -0
  13. package/extensions/ublock/_locales/bn/messages.json +4 -0
  14. package/extensions/ublock/_locales/br_FR/messages.json +4 -0
  15. package/extensions/ublock/_locales/bs/messages.json +4 -0
  16. package/extensions/ublock/_locales/ca/messages.json +4 -0
  17. package/extensions/ublock/_locales/cs/messages.json +6 -2
  18. package/extensions/ublock/_locales/cv/messages.json +4 -0
  19. package/extensions/ublock/_locales/cy/messages.json +15 -11
  20. package/extensions/ublock/_locales/da/messages.json +4 -0
  21. package/extensions/ublock/_locales/de/messages.json +8 -4
  22. package/extensions/ublock/_locales/el/messages.json +4 -0
  23. package/extensions/ublock/_locales/en/messages.json +4 -0
  24. package/extensions/ublock/_locales/en_GB/messages.json +4 -0
  25. package/extensions/ublock/_locales/eo/messages.json +4 -0
  26. package/extensions/ublock/_locales/es/messages.json +5 -1
  27. package/extensions/ublock/_locales/et/messages.json +4 -0
  28. package/extensions/ublock/_locales/eu/messages.json +4 -0
  29. package/extensions/ublock/_locales/fa/messages.json +14 -10
  30. package/extensions/ublock/_locales/fi/messages.json +7 -3
  31. package/extensions/ublock/_locales/fil/messages.json +4 -0
  32. package/extensions/ublock/_locales/fr/messages.json +5 -1
  33. package/extensions/ublock/_locales/fy/messages.json +4 -0
  34. package/extensions/ublock/_locales/gl/messages.json +4 -0
  35. package/extensions/ublock/_locales/gu/messages.json +4 -0
  36. package/extensions/ublock/_locales/he/messages.json +4 -0
  37. package/extensions/ublock/_locales/hi/messages.json +4 -0
  38. package/extensions/ublock/_locales/hr/messages.json +4 -0
  39. package/extensions/ublock/_locales/hu/messages.json +4 -0
  40. package/extensions/ublock/_locales/hy/messages.json +4 -0
  41. package/extensions/ublock/_locales/id/messages.json +4 -0
  42. package/extensions/ublock/_locales/it/messages.json +4 -0
  43. package/extensions/ublock/_locales/ja/messages.json +6 -2
  44. package/extensions/ublock/_locales/ka/messages.json +5 -1
  45. package/extensions/ublock/_locales/kk/messages.json +4 -0
  46. package/extensions/ublock/_locales/kn/messages.json +4 -0
  47. package/extensions/ublock/_locales/ko/messages.json +5 -1
  48. package/extensions/ublock/_locales/lt/messages.json +4 -0
  49. package/extensions/ublock/_locales/lv/messages.json +4 -0
  50. package/extensions/ublock/_locales/mk/messages.json +4 -0
  51. package/extensions/ublock/_locales/ml/messages.json +4 -0
  52. package/extensions/ublock/_locales/mr/messages.json +4 -0
  53. package/extensions/ublock/_locales/ms/messages.json +4 -0
  54. package/extensions/ublock/_locales/nb/messages.json +4 -0
  55. package/extensions/ublock/_locales/nl/messages.json +4 -0
  56. package/extensions/ublock/_locales/no/messages.json +4 -0
  57. package/extensions/ublock/_locales/oc/messages.json +4 -0
  58. package/extensions/ublock/_locales/pa/messages.json +5 -1
  59. package/extensions/ublock/_locales/pl/messages.json +4 -0
  60. package/extensions/ublock/_locales/pt_BR/messages.json +4 -0
  61. package/extensions/ublock/_locales/pt_PT/messages.json +4 -0
  62. package/extensions/ublock/_locales/ro/messages.json +4 -0
  63. package/extensions/ublock/_locales/ru/messages.json +5 -1
  64. package/extensions/ublock/_locales/si/messages.json +4 -0
  65. package/extensions/ublock/_locales/sk/messages.json +4 -0
  66. package/extensions/ublock/_locales/sl/messages.json +4 -0
  67. package/extensions/ublock/_locales/so/messages.json +4 -0
  68. package/extensions/ublock/_locales/sq/messages.json +4 -0
  69. package/extensions/ublock/_locales/sr/messages.json +4 -0
  70. package/extensions/ublock/_locales/sv/messages.json +5 -1
  71. package/extensions/ublock/_locales/sw/messages.json +4 -0
  72. package/extensions/ublock/_locales/ta/messages.json +4 -0
  73. package/extensions/ublock/_locales/te/messages.json +4 -0
  74. package/extensions/ublock/_locales/th/messages.json +4 -0
  75. package/extensions/ublock/_locales/tr/messages.json +4 -0
  76. package/extensions/ublock/_locales/uk/messages.json +7 -3
  77. package/extensions/ublock/_locales/ur/messages.json +4 -0
  78. package/extensions/ublock/_locales/vi/messages.json +4 -0
  79. package/extensions/ublock/_locales/zh_CN/messages.json +4 -0
  80. package/extensions/ublock/_locales/zh_TW/messages.json +4 -0
  81. package/extensions/ublock/assets/assets.json +14 -14
  82. package/extensions/ublock/assets/resources/run-at.js +80 -0
  83. package/extensions/ublock/assets/resources/safe-self.js +218 -0
  84. package/extensions/ublock/assets/resources/scriptlets.js +461 -538
  85. package/extensions/ublock/assets/resources/set-attr.js +201 -0
  86. package/extensions/ublock/assets/thirdparties/easylist/easylist.txt +4002 -11582
  87. package/extensions/ublock/assets/thirdparties/easylist/easyprivacy.txt +257 -83
  88. package/extensions/ublock/assets/thirdparties/pgl.yoyo.org/as/serverlist +22 -38
  89. package/extensions/ublock/assets/thirdparties/publicsuffix.org/list/effective_tld_names.dat +102 -110
  90. package/extensions/ublock/assets/thirdparties/urlhaus-filter/urlhaus-filter-online.txt +2776 -3613
  91. package/extensions/ublock/assets/ublock/badware.min.txt +581 -382
  92. package/extensions/ublock/assets/ublock/filters.min.txt +997 -990
  93. package/extensions/ublock/assets/ublock/privacy.min.txt +145 -26
  94. package/extensions/ublock/assets/ublock/quick-fixes.min.txt +134 -83
  95. package/extensions/ublock/assets/ublock/unbreak.min.txt +69 -46
  96. package/extensions/ublock/css/document-blocked.css +28 -1
  97. package/extensions/ublock/document-blocked.html +9 -7
  98. package/extensions/ublock/js/assets.js +15 -11
  99. package/extensions/ublock/js/benchmarks.js +13 -9
  100. package/extensions/ublock/js/biditrie.js +1 -1
  101. package/extensions/ublock/js/document-blocked.js +41 -5
  102. package/extensions/ublock/js/hntrie.js +1 -1
  103. package/extensions/ublock/js/i18n.js +2 -4
  104. package/extensions/ublock/js/pagestore.js +61 -43
  105. package/extensions/ublock/js/s14e-serializer.js +47 -22
  106. package/extensions/ublock/js/scriptlet-filtering.js +21 -18
  107. package/extensions/ublock/js/scriptlets/load-large-media-interactive.js +61 -48
  108. package/extensions/ublock/js/scriptlets/should-inject-contentscript.js +1 -3
  109. package/extensions/ublock/js/static-dnr-filtering.js +9 -7
  110. package/extensions/ublock/js/static-filtering-parser.js +87 -54
  111. package/extensions/ublock/js/static-net-filtering.js +118 -42
  112. package/extensions/ublock/js/traffic.js +37 -18
  113. package/extensions/ublock/js/vapi-background-ext.js +21 -49
  114. package/extensions/ublock/js/vapi-background.js +8 -0
  115. package/extensions/ublock/manifest.json +1 -1
  116. package/extensions/ublock/web_accessible_resources/googlesyndication_adsbygoogle.js +3 -1
  117. package/package.json +16 -16
  118. package/static/docs/swagger.json +1 -1
  119. package/static/docs/swagger.min.json +1 -1
@@ -1531,7 +1531,7 @@ export class AstFilterParser {
1531
1531
  break;
1532
1532
  }
1533
1533
  const value = this.getNetOptionValue(NODE_TYPE_NET_OPTION_NAME_URLSKIP);
1534
- if ( value.startsWith('?') === false || value.length < 2 ) {
1534
+ if ( value.length < 2 ) {
1535
1535
  this.astError = AST_ERROR_OPTION_BADVALUE;
1536
1536
  realBad = true;
1537
1537
  }
@@ -1988,6 +1988,7 @@ export class AstFilterParser {
1988
1988
  if ( parentEnd === parentBeg ) { return 0; }
1989
1989
  const s = this.getNodeString(parent);
1990
1990
  const optionsEnd = s.length;
1991
+ const parseDetails = { node: 0, len: 0 };
1991
1992
  const head = this.allocHeadNode();
1992
1993
  let prev = head, next = 0;
1993
1994
  let optionBeg = 0, optionEnd = 0;
@@ -1997,11 +1998,11 @@ export class AstFilterParser {
1997
1998
  parentBeg + optionBeg,
1998
1999
  parentBeg + optionsEnd // open ended
1999
2000
  );
2000
- const { node: down, len: optionLen } = this.parseNetOption(next);
2001
+ this.parseNetOption(next, parseDetails);
2001
2002
  // set next's end to down's end
2002
- optionEnd += optionLen;
2003
+ optionEnd += parseDetails.len;
2003
2004
  this.nodes[next+NODE_END_INDEX] = parentBeg + optionEnd;
2004
- this.linkDown(next, down);
2005
+ this.linkDown(next, parseDetails.node);
2005
2006
  prev = this.linkRight(prev, next);
2006
2007
  if ( optionEnd === optionsEnd ) { break; }
2007
2008
  optionBeg = optionEnd + 1;
@@ -2010,7 +2011,7 @@ export class AstFilterParser {
2010
2011
  parentBeg + optionEnd,
2011
2012
  parentBeg + optionBeg
2012
2013
  );
2013
- if ( optionLen === 0 || optionBeg === optionsEnd ) {
2014
+ if ( parseDetails.len === 0 || optionBeg === optionsEnd ) {
2014
2015
  this.addNodeFlags(next, NODE_FLAG_ERROR);
2015
2016
  this.addFlags(AST_FLAG_HAS_ERROR);
2016
2017
  }
@@ -2023,7 +2024,7 @@ export class AstFilterParser {
2023
2024
  return this.throwHeadNode(head);
2024
2025
  }
2025
2026
 
2026
- parseNetOption(parent) {
2027
+ parseNetOption(parent, parseDetails) {
2027
2028
  const parentBeg = this.nodes[parent+NODE_BEG_INDEX];
2028
2029
  const s = this.getNodeString(parent);
2029
2030
  const match = this.reNetOption.exec(s) || [];
@@ -2031,7 +2032,9 @@ export class AstFilterParser {
2031
2032
  this.addNodeFlags(parent, NODE_FLAG_ERROR);
2032
2033
  this.addFlags(AST_FLAG_HAS_ERROR);
2033
2034
  this.astError = AST_ERROR_OPTION_UNKNOWN;
2034
- return { node: 0, len: s.length };
2035
+ parseDetails.node = 0;
2036
+ parseDetails.len = s.length;
2037
+ return;
2035
2038
  }
2036
2039
  const head = this.allocHeadNode();
2037
2040
  let prev = head, next = 0;
@@ -2073,7 +2076,9 @@ export class AstFilterParser {
2073
2076
  }
2074
2077
  prev = this.linkRight(prev, next);
2075
2078
  if ( assigned === false ) {
2076
- return { node: this.throwHeadNode(head), len: matchEnd };
2079
+ parseDetails.node = this.throwHeadNode(head);
2080
+ parseDetails.len = matchEnd;
2081
+ return;
2077
2082
  }
2078
2083
  next = this.allocTypedNode(
2079
2084
  NODE_TYPE_NET_OPTION_ASSIGN,
@@ -2129,7 +2134,8 @@ export class AstFilterParser {
2129
2134
  );
2130
2135
  this.linkRight(prev, next);
2131
2136
  }
2132
- return { node: this.throwHeadNode(head), len: details.quoteEnd };
2137
+ parseDetails.node = this.throwHeadNode(head);
2138
+ parseDetails.len = details.quoteEnd;
2133
2139
  }
2134
2140
 
2135
2141
  endOfNetOption(s, beg) {
@@ -2156,32 +2162,26 @@ export class AstFilterParser {
2156
2162
  );
2157
2163
  if ( parentEnd === parentBeg ) { return containerNode; }
2158
2164
  const separatorCode = separator.charCodeAt(0);
2165
+ const parseDetails = { separator, mode, node: 0, len: 0 };
2159
2166
  const listNode = this.allocHeadNode();
2160
2167
  let prev = listNode;
2161
2168
  let domainNode = 0;
2162
2169
  let separatorNode = 0;
2163
2170
  const s = this.getNodeString(parent);
2164
2171
  const listEnd = s.length;
2165
- let beg = 0, end = 0, c = 0;
2172
+ let beg = 0, end = 0;
2166
2173
  while ( beg < listEnd ) {
2167
- c = s.charCodeAt(beg);
2168
- if ( c === 0x7E /* ~ */ ) {
2169
- c = s.charCodeAt(beg+1) || 0;
2170
- }
2171
- if ( c !== 0x2F /* / */ ) {
2172
- end = s.indexOf(separator, beg);
2173
- } else {
2174
- end = s.indexOf('/', beg+1);
2175
- end = s.indexOf(separator, end !== -1 ? end+1 : beg);
2176
- }
2177
- if ( end === -1 ) { end = listEnd; }
2174
+ const next = this.allocTypedNode(
2175
+ NODE_TYPE_OPTION_VALUE_DOMAIN_RAW,
2176
+ parentBeg + beg,
2177
+ parentBeg + listEnd // open ended
2178
+ );
2179
+ this.parseDomain(next, parseDetails);
2180
+ end = beg + parseDetails.len;
2181
+ this.nodes[next+NODE_END_INDEX] = parentBeg + end;
2178
2182
  if ( end !== beg ) {
2179
- domainNode = this.allocTypedNode(
2180
- NODE_TYPE_OPTION_VALUE_DOMAIN_RAW,
2181
- parentBeg + beg,
2182
- parentBeg + end
2183
- );
2184
- this.linkDown(domainNode, this.parseDomain(domainNode, mode));
2183
+ domainNode = next;
2184
+ this.linkDown(domainNode, parseDetails.node);
2185
2185
  prev = this.linkRight(prev, domainNode);
2186
2186
  } else {
2187
2187
  domainNode = 0;
@@ -2217,23 +2217,38 @@ export class AstFilterParser {
2217
2217
  return containerNode;
2218
2218
  }
2219
2219
 
2220
- parseDomain(parent, mode = 0b0000) {
2220
+ parseDomain(parent, parseDetails) {
2221
2221
  const parentBeg = this.nodes[parent+NODE_BEG_INDEX];
2222
2222
  const parentEnd = this.nodes[parent+NODE_END_INDEX];
2223
+ const not = this.charCodeAt(parentBeg) === 0x7E /* ~ */;
2223
2224
  let head = 0, next = 0;
2224
2225
  let beg = parentBeg;
2225
- const c = this.charCodeAt(beg);
2226
- if ( c === 0x7E /* ~ */ ) {
2226
+ if ( not ) {
2227
2227
  this.addNodeFlags(parent, NODE_FLAG_IS_NEGATED);
2228
2228
  head = this.allocTypedNode(NODE_TYPE_OPTION_VALUE_NOT, beg, beg + 1);
2229
- if ( (mode & 0b1000) === 0 ) {
2229
+ if ( (parseDetails.mode & 0b1000) === 0 ) {
2230
2230
  this.addNodeFlags(parent, NODE_FLAG_ERROR);
2231
2231
  }
2232
2232
  beg += 1;
2233
2233
  }
2234
- if ( beg !== parentEnd ) {
2235
- next = this.allocTypedNode(NODE_TYPE_OPTION_VALUE_DOMAIN, beg, parentEnd);
2236
- const hn = this.normalizeDomainValue(this.getNodeString(next), mode);
2234
+ const c0 = this.charCodeAt(beg);
2235
+ let end = beg;
2236
+ let type = 0;
2237
+ if ( c0 === 0x2F /* / */ ) {
2238
+ end = this.indexOf('/', beg + 1, parentEnd);
2239
+ if ( end !== -1 ) { end += 1; }
2240
+ type = 1;
2241
+ } else if ( c0 === 0x5B /* [ */ && this.startsWith('[$domain=/', beg) ) {
2242
+ end = this.indexOf('/]', beg + 10, parentEnd);
2243
+ if ( end !== -1 ) { end += 2; }
2244
+ type = 2;
2245
+ } else {
2246
+ end = this.indexOf(parseDetails.separator, end, parentEnd);
2247
+ }
2248
+ if ( end === -1 ) { end = parentEnd; }
2249
+ if ( beg !== end ) {
2250
+ next = this.allocTypedNode(NODE_TYPE_OPTION_VALUE_DOMAIN, beg, end);
2251
+ const hn = this.normalizeDomainValue(next, type, parseDetails.mode);
2237
2252
  if ( hn !== undefined ) {
2238
2253
  if ( hn !== '' ) {
2239
2254
  this.setNodeTransform(next, hn);
@@ -2252,7 +2267,8 @@ export class AstFilterParser {
2252
2267
  this.addNodeFlags(parent, NODE_FLAG_ERROR);
2253
2268
  this.addFlags(AST_FLAG_HAS_ERROR);
2254
2269
  }
2255
- return head;
2270
+ parseDetails.node = head;
2271
+ parseDetails.len = end - parentBeg;
2256
2272
  }
2257
2273
 
2258
2274
  // mode bits:
@@ -2261,16 +2277,16 @@ export class AstFilterParser {
2261
2277
  // 0b00100: can use single wildcard
2262
2278
  // 0b01000: can be negated
2263
2279
  // 0b10000: can be a regex
2264
- normalizeDomainValue(s, modeBits) {
2265
- if ( (modeBits & 0b10000) === 0 ||
2266
- s.length <= 2 ||
2267
- s.charCodeAt(0) !== 0x2F /* / */ ||
2268
- exCharCodeAt(s, -1) !== 0x2F /* / */
2269
- ) {
2280
+ normalizeDomainValue(node, type, modeBits) {
2281
+ const s = this.getNodeString(node);
2282
+ if ( type === 0 ) {
2270
2283
  return this.normalizeHostnameValue(s, modeBits);
2271
2284
  }
2272
- const source = this.normalizeRegexPattern(s);
2285
+ if ( (modeBits & 0b10000) === 0 ) { return ''; }
2286
+ const regex = type === 1 ? s : `/${s.slice(10, -2)}/`;
2287
+ const source = this.normalizeRegexPattern(regex);
2273
2288
  if ( source === '' ) { return ''; }
2289
+ if ( type === 1 && source === regex ) { return; }
2274
2290
  return `/${source}/`;
2275
2291
  }
2276
2292
 
@@ -2857,6 +2873,15 @@ export class AstFilterParser {
2857
2873
  return pos < this.rawEnd ? this.raw.charCodeAt(pos) : -1;
2858
2874
  }
2859
2875
 
2876
+ indexOf(needle, beg, end = 0) {
2877
+ const haystack = end === 0 ? this.raw : this.raw.slice(0, end);
2878
+ return haystack.indexOf(needle, beg);
2879
+ }
2880
+
2881
+ startsWith(s, pos) {
2882
+ return pos < this.rawEnd && this.raw.startsWith(s, pos);
2883
+ }
2884
+
2860
2885
  isTokenCharCode(c) {
2861
2886
  return c === 0x25 ||
2862
2887
  c >= 0x30 && c <= 0x39 ||
@@ -4116,9 +4141,11 @@ class ExtSelectorCompiler {
4116
4141
  compileXpathExpression(s) {
4117
4142
  const r = this.unquoteString(s);
4118
4143
  if ( r.i !== s.length ) { return; }
4119
- if ( globalThis.document instanceof Object === false ) { return r.s; }
4144
+ const doc = globalThis.document;
4145
+ if ( doc instanceof Object === false ) { return r.s; }
4120
4146
  try {
4121
- globalThis.document.createExpression(r.s, null);
4147
+ const expr = doc.createExpression(r.s, null);
4148
+ expr.evaluate(doc, XPathResult.ANY_UNORDERED_NODE_TYPE);
4122
4149
  } catch (e) {
4123
4150
  return;
4124
4151
  }
@@ -4362,7 +4389,7 @@ export const utils = (( ) => {
4362
4389
  // only ABP.
4363
4390
  [ 'ext_abp', 'false' ],
4364
4391
  // Compatibility with other blockers
4365
- // https://kb.adguard.com/en/general/how-to-create-your-own-ad-filters#adguard-specific
4392
+ // https://adguard.com/kb/general/ad-filtering/create-own-filters/#conditions-directive
4366
4393
  [ 'adguard', 'adguard' ],
4367
4394
  [ 'adguard_app_android', 'false' ],
4368
4395
  [ 'adguard_app_ios', 'false' ],
@@ -4430,14 +4457,14 @@ export const utils = (( ) => {
4430
4457
  const parts = [ 0 ];
4431
4458
  let discard = false;
4432
4459
 
4433
- const shouldDiscard = ( ) => stack.some(v => v);
4460
+ const shouldDiscard = ( ) => stack.some(v => v.known && v.discard);
4434
4461
 
4435
- const begif = (startDiscard, match) => {
4436
- if ( discard === false && startDiscard ) {
4437
- parts.push(match.index);
4462
+ const begif = details => {
4463
+ if ( discard === false && details.known && details.discard ) {
4464
+ parts.push(details.pos);
4438
4465
  discard = true;
4439
4466
  }
4440
- stack.push(startDiscard);
4467
+ stack.push(details);
4441
4468
  };
4442
4469
 
4443
4470
  const endif = match => {
@@ -4455,15 +4482,21 @@ export const utils = (( ) => {
4455
4482
 
4456
4483
  switch ( match[1] ) {
4457
4484
  case 'if': {
4458
- const startDiscard = this.evaluateExpr(match[2].trim(), env) === false;
4459
- begif(startDiscard, match);
4485
+ const result = this.evaluateExpr(match[2].trim(), env);
4486
+ begif({
4487
+ known: result !== undefined,
4488
+ discard: result === false,
4489
+ pos: match.index,
4490
+ });
4460
4491
  break;
4461
4492
  }
4462
4493
  case 'else': {
4463
4494
  if ( stack.length === 0 ) { break; }
4464
- const startDiscard = stack[stack.length-1] === false;
4495
+ const details = stack[stack.length-1];
4465
4496
  endif(match);
4466
- begif(startDiscard, match);
4497
+ details.discard = details.discard === false;
4498
+ details.pos = match.index;
4499
+ begif(details);
4467
4500
  break;
4468
4501
  }
4469
4502
  case 'endif': {
@@ -3048,9 +3048,6 @@ class FilterIPAddress {
3048
3048
  if ( ipaddr.startsWith('::ffff:') === false ) { return false; }
3049
3049
  return this.reIPv6IPv4lan.test(ipaddr);
3050
3050
  }
3051
- if ( c0 === 0x36 /* 6 */ ) {
3052
- return ipaddr.startsWith('64:ff9b:');
3053
- }
3054
3051
  if ( c0 === 0x66 /* f */ ) {
3055
3052
  return this.reIPv6local.test(ipaddr);
3056
3053
  }
@@ -4789,20 +4786,13 @@ StaticNetFilteringEngine.prototype.toSelfie = function() {
4789
4786
  };
4790
4787
  };
4791
4788
 
4792
- StaticNetFilteringEngine.prototype.serialize = async function() {
4793
- const selfie = [];
4794
- const storage = {
4795
- put(name, data) {
4796
- selfie.push([ name, data ]);
4797
- }
4798
- };
4799
- await this.toSelfie(storage, '');
4800
- return JSON.stringify(selfie);
4789
+ StaticNetFilteringEngine.prototype.serialize = function() {
4790
+ return this.toSelfie();
4801
4791
  };
4802
4792
 
4803
4793
  /******************************************************************************/
4804
4794
 
4805
- StaticNetFilteringEngine.prototype.fromSelfie = async function(selfie) {
4795
+ StaticNetFilteringEngine.prototype.fromSelfie = function(selfie) {
4806
4796
  if ( typeof selfie !== 'object' || selfie === null ) { return; }
4807
4797
 
4808
4798
  this.reset();
@@ -4835,14 +4825,8 @@ StaticNetFilteringEngine.prototype.fromSelfie = async function(selfie) {
4835
4825
  return true;
4836
4826
  };
4837
4827
 
4838
- StaticNetFilteringEngine.prototype.unserialize = async function(s) {
4839
- const selfie = new Map(JSON.parse(s));
4840
- const storage = {
4841
- async get(name) {
4842
- return { content: selfie.get(name) };
4843
- }
4844
- };
4845
- return this.fromSelfie(storage, '');
4828
+ StaticNetFilteringEngine.prototype.unserialize = function(selfie) {
4829
+ return this.fromSelfie(selfie);
4846
4830
  };
4847
4831
 
4848
4832
  /******************************************************************************/
@@ -5369,12 +5353,10 @@ StaticNetFilteringEngine.prototype.transformRequest = function(fctxt, out = [])
5369
5353
  out.push(directive);
5370
5354
  continue;
5371
5355
  }
5372
- const { refs } = directive;
5373
- if ( refs instanceof Object === false ) { continue; }
5374
- if ( refs.$cache === null ) {
5375
- refs.$cache = sfp.parseReplaceValue(refs.value);
5356
+ if ( directive.cache === null ) {
5357
+ directive.cache = sfp.parseReplaceValue(directive.value);
5376
5358
  }
5377
- const cache = refs.$cache;
5359
+ const cache = directive.cache;
5378
5360
  if ( cache === undefined ) { continue; }
5379
5361
  const before = `${redirectURL.pathname}${redirectURL.search}${redirectURL.hash}`;
5380
5362
  if ( cache.re.test(before) !== true ) { continue; }
@@ -5395,9 +5377,60 @@ StaticNetFilteringEngine.prototype.transformRequest = function(fctxt, out = [])
5395
5377
  return out;
5396
5378
  };
5397
5379
 
5398
- /******************************************************************************/
5399
-
5400
- StaticNetFilteringEngine.prototype.urlSkip = function(fctxt, out = []) {
5380
+ /**
5381
+ * @trustedOption urlskip
5382
+ *
5383
+ * @description
5384
+ * Extract a URL from another URL according to one or more transformation steps,
5385
+ * thereby skipping over intermediate network request(s) to remote servers.
5386
+ * Requires a trusted source.
5387
+ *
5388
+ * @param steps
5389
+ * A serie of space-separated directives representing the transformation steps
5390
+ * to perform to extract the final URL to which a network request should be
5391
+ * redirected.
5392
+ *
5393
+ * Supported directives:
5394
+ *
5395
+ * `?name`: extract the value of parameter `name` as the current string.
5396
+ *
5397
+ * `&i`: extract the name of the parameter at position `i` as the current
5398
+ * string. The position is 1-based.
5399
+ *
5400
+ * `/.../`: extract the first capture group of a regex as the current string.
5401
+ *
5402
+ * `+https`: prepend the current string with `https://`.
5403
+ *
5404
+ * `-base64`: decode the current string as a base64-encoded string.
5405
+ *
5406
+ * `-uricomponent`: decode the current string as a URI encoded string.
5407
+ *
5408
+ * `-blocked`: allow the redirection of blocked requests. By default, blocked
5409
+ * requests can't by urlskip'ed.
5410
+ *
5411
+ * At any given step, the currently extracted string may not necessarily be
5412
+ * a valid URL, and more transformation steps may be needed to obtain a valid
5413
+ * URL once all the steps are applied.
5414
+ *
5415
+ * An unsupported step or a failed step will abort the transformation and no
5416
+ * redirection will be performed.
5417
+ *
5418
+ * The final step is expected to yield a valid URL. If the result is not a
5419
+ * valid URL, no redirection will be performed.
5420
+ *
5421
+ * @example
5422
+ * ||example.com/path/to/tracker$urlskip=?url
5423
+ * ||example.com/path/to/tracker$urlskip=?url ?to
5424
+ * ||pixiv.net/jump.php?$urlskip=&1
5425
+ * ||podtrac.com/pts/redirect.mp3/$urlskip=/\/redirect\.mp3\/(.*?\.mp3\b)/ +https
5426
+ *
5427
+ * */
5428
+
5429
+ StaticNetFilteringEngine.prototype.urlSkip = function(
5430
+ fctxt,
5431
+ blocked,
5432
+ out = []
5433
+ ) {
5401
5434
  if ( fctxt.redirectURL !== undefined ) { return; }
5402
5435
  const directives = this.matchAndFetchModifiers(fctxt, 'urlskip');
5403
5436
  if ( directives === undefined ) { return; }
@@ -5409,7 +5442,7 @@ StaticNetFilteringEngine.prototype.urlSkip = function(fctxt, out = []) {
5409
5442
  const urlin = fctxt.url;
5410
5443
  const value = directive.value;
5411
5444
  const steps = value.includes(' ') && value.split(/ +/) || [ value ];
5412
- const urlout = urlSkip(urlin, steps);
5445
+ const urlout = urlSkip(directive, urlin, blocked, steps);
5413
5446
  if ( urlout === undefined ) { continue; }
5414
5447
  if ( urlout === urlin ) { continue; }
5415
5448
  fctxt.redirectURL = urlout;
@@ -5420,31 +5453,74 @@ StaticNetFilteringEngine.prototype.urlSkip = function(fctxt, out = []) {
5420
5453
  return out;
5421
5454
  };
5422
5455
 
5423
- function urlSkip(urlin, steps) {
5456
+ function urlSkip(directive, url, blocked, steps) {
5424
5457
  try {
5425
- let urlout;
5458
+ let redirectBlocked = false;
5459
+ let urlout = url;
5426
5460
  for ( const step of steps ) {
5461
+ const urlin = urlout;
5462
+ const c0 = step.charCodeAt(0);
5463
+ // Extract from URL parameter name at position i
5464
+ if ( c0 === 0x26 ) { // &
5465
+ const i = (parseInt(step.slice(1)) || 0) - 1;
5466
+ if ( i < 0 ) { return; }
5467
+ const url = new URL(urlin);
5468
+ if ( i >= url.searchParams.size ) { return; }
5469
+ const params = Array.from(url.searchParams.keys());
5470
+ urlout = decodeURIComponent(params[i]);
5471
+ continue;
5472
+ }
5473
+ // Enforce https
5474
+ if ( c0 === 0x2B && step === '+https' ) {
5475
+ const s = urlin.replace(/^https?:\/\//, '');
5476
+ if ( /^[\w-]:\/\//.test(s) ) { return; }
5477
+ urlout = `https://${s}`;
5478
+ continue;
5479
+ }
5480
+ // Decode
5481
+ if ( c0 === 0x2D ) {
5482
+ // Base64
5483
+ if ( step === '-base64' ) {
5484
+ urlout = self.atob(urlin);
5485
+ continue;
5486
+ }
5487
+ // URI component
5488
+ if ( step === '-uricomponent' ) {
5489
+ urlout = self.decodeURIComponent(urlin);
5490
+ continue;
5491
+ }
5492
+ // Enable skip of blocked requests
5493
+ if ( step === '-blocked' ) {
5494
+ redirectBlocked = true;
5495
+ continue;
5496
+ }
5497
+ }
5498
+ // Regex extraction from first capture group
5499
+ if ( c0 === 0x2F ) { // /
5500
+ if ( directive.cache === null ) {
5501
+ directive.cache = new RegExp(step.slice(1, -1));
5502
+ }
5503
+ const match = directive.cache.exec(urlin);
5504
+ if ( match === null ) { return; }
5505
+ if ( match.length <= 1 ) { return; }
5506
+ urlout = match[1];
5507
+ continue;
5508
+ }
5427
5509
  // Extract from URL parameter
5428
- if ( step.startsWith('?') ) {
5510
+ if ( c0 === 0x3F ) { // ?
5429
5511
  urlout = (new URL(urlin)).searchParams.get(step.slice(1));
5430
5512
  if ( urlout === null ) { return; }
5431
5513
  if ( urlout.includes(' ') ) {
5432
5514
  urlout = urlout.replace(/ /g, '%20');
5433
5515
  }
5434
- urlin = urlout;
5435
- continue;
5436
- }
5437
- // Enforce https
5438
- if ( step === '+https' ) {
5439
- const s = urlin.replace(/^https?:\/\//, '');
5440
- if ( /^[\w-]:\/\//.test(s) ) { return; }
5441
- urlin = urlout = `https://${s}`;
5442
5516
  continue;
5443
5517
  }
5444
5518
  // Unknown directive
5445
5519
  return;
5446
5520
  }
5447
- void new URL(urlout);
5521
+ const urlfinal = new URL(urlout);
5522
+ if ( urlfinal.protocol !== 'https:' ) { return; }
5523
+ if ( blocked && redirectBlocked !== true ) { return; }
5448
5524
  return urlout;
5449
5525
  } catch(x) {
5450
5526
  }
@@ -196,8 +196,9 @@ const onBeforeRootFrameRequest = function(fctxt) {
196
196
  if ( trusted === false && pageStore !== null ) {
197
197
  if ( result !== 1 ) {
198
198
  pageStore.redirectNonBlockedRequest(fctxt);
199
+ } else {
200
+ pageStore.skipMainDocument(fctxt, true);
199
201
  }
200
- pageStore.skipMainDocument(fctxt);
201
202
  }
202
203
 
203
204
  if ( logger.enabled ) {
@@ -215,16 +216,20 @@ const onBeforeRootFrameRequest = function(fctxt) {
215
216
  if ( result !== 1 ) { return; }
216
217
 
217
218
  // No log data means no strict blocking (because we need to report why
218
- // the blocking occurs.
219
+ // the blocking occurs
219
220
  if ( logData === undefined ) { return; }
220
221
 
221
222
  // Blocked
222
223
 
224
+ // Find out the URL navigated to should the document not be strict-blocked
225
+ pageStore.skipMainDocument(fctxt, false);
226
+
223
227
  const query = encodeURIComponent(JSON.stringify({
224
228
  url: requestURL,
225
- hn: requestHostname,
226
229
  dn: fctxt.getDomain() || requestHostname,
227
- fs: logData.raw
230
+ fs: logData.raw,
231
+ hn: requestHostname,
232
+ to: fctxt.redirectURL || '',
228
233
  }));
229
234
 
230
235
  vAPI.tabs.replace(
@@ -488,7 +493,7 @@ const onHeadersReceived = function(details) {
488
493
  }
489
494
  if ( pageStore.getNetFilteringSwitch(fctxt) === false ) { return; }
490
495
 
491
- if ( fctxt.itype === fctxt.IMAGE || fctxt.itype === fctxt.MEDIA ) {
496
+ if ( (fctxt.itype & foilLargeMediaElement.TYPE_BITS) !== 0 ) {
492
497
  const result = foilLargeMediaElement(details, fctxt, pageStore);
493
498
  if ( result !== undefined ) { return result; }
494
499
  }
@@ -1124,15 +1129,12 @@ const injectPP = function(fctxt, pageStore, responseHeaders) {
1124
1129
  const foilLargeMediaElement = function(details, fctxt, pageStore) {
1125
1130
  if ( details.fromCache === true ) { return; }
1126
1131
 
1127
- let size = 0;
1128
- if ( µb.userSettings.largeMediaSize !== 0 ) {
1129
- const headers = details.responseHeaders;
1130
- const i = headerIndexFromName('content-length', headers);
1131
- if ( i === -1 ) { return; }
1132
- size = parseInt(headers[i].value, 10) || 0;
1133
- }
1132
+ onDemandHeaders.setHeaders(details.responseHeaders);
1133
+
1134
+ const result = pageStore.filterLargeMediaElement(fctxt, onDemandHeaders);
1135
+
1136
+ onDemandHeaders.reset();
1134
1137
 
1135
- const result = pageStore.filterLargeMediaElement(fctxt, size);
1136
1138
  if ( result === 0 ) { return; }
1137
1139
 
1138
1140
  if ( logger.enabled ) {
@@ -1142,16 +1144,15 @@ const foilLargeMediaElement = function(details, fctxt, pageStore) {
1142
1144
  return { cancel: true };
1143
1145
  };
1144
1146
 
1147
+ foilLargeMediaElement.TYPE_BITS = fc.IMAGE | fc.MEDIA | fc.XMLHTTPREQUEST;
1148
+
1145
1149
  /******************************************************************************/
1146
1150
 
1147
1151
  // Caller must ensure headerName is normalized to lower case.
1148
1152
 
1149
1153
  const headerIndexFromName = function(headerName, headers) {
1150
- let i = headers.length;
1151
- while ( i-- ) {
1152
- if ( headers[i].name.toLowerCase() === headerName ) {
1153
- return i;
1154
- }
1154
+ for ( let i = 0, n = headers.length; i < n; i++ ) {
1155
+ if ( headers[i].name.toLowerCase() === headerName ) { return i; }
1155
1156
  }
1156
1157
  return -1;
1157
1158
  };
@@ -1161,6 +1162,24 @@ const headerValueFromName = function(headerName, headers) {
1161
1162
  return i !== -1 ? headers[i].value : '';
1162
1163
  };
1163
1164
 
1165
+ const onDemandHeaders = {
1166
+ headers: [],
1167
+ get contentLength() {
1168
+ const contentLength = headerValueFromName('content-length', this.headers);
1169
+ if ( contentLength === '' ) { return Number.NaN; }
1170
+ return parseInt(contentLength, 10) || 0;
1171
+ },
1172
+ get contentType() {
1173
+ return headerValueFromName('content-type', this.headers);
1174
+ },
1175
+ setHeaders(headers) {
1176
+ this.headers = headers;
1177
+ },
1178
+ reset() {
1179
+ this.headers = [];
1180
+ }
1181
+ };
1182
+
1164
1183
  /******************************************************************************/
1165
1184
 
1166
1185
  const strictBlockBypasser = {