@@ -5356,12 +5356,10 @@ StaticNetFilteringEngine.prototype.transformRequest = function(fctxt, out = [])
53565356 out . push ( directive ) ;
53575357 continue ;
53585358 }
5359- const { refs } = directive ;
5360- if ( refs instanceof Object === false ) { continue ; }
5361- if ( refs . $cache === null ) {
5362- refs . $cache = sfp . parseReplaceValue ( refs . value ) ;
5359+ if ( directive . cache === null ) {
5360+ directive . cache = sfp . parseReplaceValue ( directive . value ) ;
53635361 }
5364- const cache = refs . $ cache;
5362+ const cache = directive . cache ;
53655363 if ( cache === undefined ) { continue ; }
53665364 const before = `${ redirectURL . pathname } ${ redirectURL . search } ${ redirectURL . hash } ` ;
53675365 if ( cache . re . test ( before ) !== true ) { continue ; }
@@ -5382,7 +5380,49 @@ StaticNetFilteringEngine.prototype.transformRequest = function(fctxt, out = [])
53825380 return out ;
53835381} ;
53845382
5385- /******************************************************************************/
5383+ /**
5384+ * @trustedOption urlkip
5385+ *
5386+ * @description
5387+ * Extract a URL from another URL according to one or more transformation steps,
5388+ * thereby skipping over intermediate network request(s) to remote servers.
5389+ * Requires a trusted source.
5390+ *
5391+ * @param steps
5392+ * A serie of space-separated directives representing the transformation steps
5393+ * to perform to extract the final URL to which a network request should be
5394+ * redirected.
5395+ *
5396+ * Supported directives:
5397+ *
5398+ * `?name`: extract the value of parameter `name` as the current string.
5399+ *
5400+ * `&i`: extract the name of the parameter at position `i` as the current
5401+ * string. The position is 1-based.
5402+ *
5403+ * `/.../`: extract the first capture group of a regex as the current string.
5404+ *
5405+ * `+https`: prepend the current string with `https://`.
5406+ *
5407+ * `-base64`: decode the current string as a base64-encoded string.
5408+ *
5409+ * At any given step, the currently extracted string may not necessarily be
5410+ * a valid URL, and more transformation steps may be needed to obtain a valid
5411+ * URL once all the steps are applied.
5412+ *
5413+ * An unsupported step or a failed step will abort the transformation and no
5414+ * redirection will be performed.
5415+ *
5416+ * The final step is expected to yield a valid URL. If the result is not a
5417+ * valid URL, no redirection will be performed.
5418+ *
5419+ * @example
5420+ * ||example.com/path/to/tracker$urlskip=?url
5421+ * ||example.com/path/to/tracker$urlskip=?url ?to
5422+ * ||pixiv.net/jump.php?$urlskip=&1
5423+ * ||podtrac.com/pts/redirect.mp3/$urlskip=/podtrac\.com\/pts\/redirect\.mp3\/(.*?\.mp3\b)/ +https
5424+ *
5425+ * */
53865426
53875427StaticNetFilteringEngine . prototype . urlSkip = function ( fctxt , out = [ ] ) {
53885428 if ( fctxt . redirectURL !== undefined ) { return ; }
@@ -5396,7 +5436,7 @@ StaticNetFilteringEngine.prototype.urlSkip = function(fctxt, out = []) {
53965436 const urlin = fctxt . url ;
53975437 const value = directive . value ;
53985438 const steps = value . includes ( ' ' ) && value . split ( / + / ) || [ value ] ;
5399- const urlout = urlSkip ( urlin , steps ) ;
5439+ const urlout = urlSkip ( directive , urlin , steps ) ;
54005440 if ( urlout === undefined ) { continue ; }
54015441 if ( urlout === urlin ) { continue ; }
54025442 fctxt . redirectURL = urlout ;
@@ -5407,41 +5447,52 @@ StaticNetFilteringEngine.prototype.urlSkip = function(fctxt, out = []) {
54075447 return out ;
54085448} ;
54095449
5410- function urlSkip ( urlin , steps ) {
5450+ function urlSkip ( directive , urlin , steps ) {
54115451 try {
5412- let urlout ;
5452+ let urlout = urlin ;
54135453 for ( const step of steps ) {
5454+ const urlin = urlout ;
54145455 const c0 = step . charCodeAt ( 0 ) ;
5415- // Extract from URL parameter
5416- if ( c0 === 0x3F ) { /* ? */
5417- urlout = ( new URL ( urlin ) ) . searchParams . get ( step . slice ( 1 ) ) ;
5418- if ( urlout === null ) { return ; }
5419- if ( urlout . includes ( ' ' ) ) {
5420- urlout = urlout . replace ( / / g, '%20' ) ;
5421- }
5422- urlin = urlout ;
5423- continue ;
5424- }
54255456 // Extract from URL parameter name at position i
5426- if ( c0 === 0x26 ) { /* & */
5457+ if ( c0 === 0x26 ) { // &
54275458 const i = ( parseInt ( step . slice ( 1 ) ) || 0 ) - 1 ;
54285459 if ( i < 0 ) { return ; }
54295460 const url = new URL ( urlin ) ;
54305461 if ( i >= url . searchParams . size ) { return ; }
54315462 const params = Array . from ( url . searchParams . keys ( ) ) ;
5432- urlin = urlout = decodeURIComponent ( params [ i ] ) ;
5463+ urlout = decodeURIComponent ( params [ i ] ) ;
54335464 continue ;
54345465 }
54355466 // Enforce https
5436- if ( step === '+https' ) {
5467+ if ( c0 === 0x2B && step === '+https' ) {
54375468 const s = urlin . replace ( / ^ h t t p s ? : \/ \/ / , '' ) ;
54385469 if ( / ^ [ \w - ] : \/ \/ / . test ( s ) ) { return ; }
5439- urlin = urlout = `https://${ s } ` ;
5470+ urlout = `https://${ s } ` ;
54405471 continue ;
54415472 }
54425473 // Decode base64
5443- if ( step === '-base64' ) {
5444- urlin = urlout = self . atob ( urlin ) ;
5474+ if ( c0 === 0x2D && step === '-base64' ) {
5475+ urlout = self . atob ( urlin ) ;
5476+ continue ;
5477+ }
5478+ // Regex extraction from first capture group
5479+ if ( c0 === 0x2F ) { // /
5480+ if ( directive . cache === null ) {
5481+ directive . cache = new RegExp ( step . slice ( 1 , - 1 ) ) ;
5482+ }
5483+ const match = directive . cache . exec ( urlin ) ;
5484+ if ( match === null ) { return ; }
5485+ if ( match . length <= 1 ) { return ; }
5486+ urlout = match [ 1 ] ;
5487+ continue ;
5488+ }
5489+ // Extract from URL parameter
5490+ if ( c0 === 0x3F ) { // ?
5491+ urlout = ( new URL ( urlin ) ) . searchParams . get ( step . slice ( 1 ) ) ;
5492+ if ( urlout === null ) { return ; }
5493+ if ( urlout . includes ( ' ' ) ) {
5494+ urlout = urlout . replace ( / / g, '%20' ) ;
5495+ }
54455496 continue ;
54465497 }
54475498 // Unknown directive
0 commit comments