/******************************************************************************* uBlock Origin - a comprehensive, efficient content blocker Copyright (C) 2019-present Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see {http://www.gnu.org/licenses/}. Home: https://github.com/gorhill/uBlock */ import { registerScriptlet } from './base.js'; import { safeSelf } from './safe-self.js'; // Externally added to the private namespace in which scriptlets execute. /* global scriptletGlobals */ /******************************************************************************/ export function getRandomTokenFn() { const safe = safeSelf(); return safe.String_fromCharCode(Date.now() % 26 + 97) + safe.Math_floor(safe.Math_random() * 982451653 + 982451653).toString(36); } registerScriptlet(getRandomTokenFn, { name: 'get-random-token.fn', dependencies: [ safeSelf, ], }); /******************************************************************************/ export function getExceptionTokenFn() { const token = getRandomTokenFn(); const oe = self.onerror; self.onerror = function(msg, ...args) { if ( typeof msg === 'string' && msg.includes(token) ) { return true; } if ( oe instanceof Function ) { return oe.call(this, msg, ...args); } }.bind(); return token; } registerScriptlet(getExceptionTokenFn, { name: 'get-exception-token.fn', dependencies: [ getRandomTokenFn, ], }); /******************************************************************************/ export function parsePropertiesToMatchFn(propsToMatch, implicit = '') { const safe = safeSelf(); const needles = new Map(); if ( propsToMatch === undefined || propsToMatch === '' ) { return needles; } const options = { canNegate: true }; for ( const needle of safe.String_split.call(propsToMatch, /\s+/) ) { let [ prop, pattern ] = safe.String_split.call(needle, ':'); if ( prop === '' ) { continue; } if ( pattern !== undefined && /[^$\w -]/.test(prop) ) { prop = `${prop}:${pattern}`; pattern = undefined; } if ( pattern !== undefined ) { needles.set(prop, safe.initPattern(pattern, options)); } else if ( implicit !== '' ) { needles.set(implicit, safe.initPattern(prop, options)); } } return needles; } registerScriptlet(parsePropertiesToMatchFn, { name: 'parse-properties-to-match.fn', dependencies: [ safeSelf, ], }); /******************************************************************************/ export function matchObjectPropertiesFn(propNeedles, ...objs) { const safe = safeSelf(); const matched = []; for ( const obj of objs ) { if ( obj instanceof Object === false ) { continue; } for ( const [ prop, details ] of propNeedles ) { let value = obj[prop]; if ( value === undefined ) { continue; } if ( typeof value !== 'string' ) { try { value = safe.JSON_stringify(value); } catch { } if ( typeof value !== 'string' ) { continue; } } if ( safe.testPattern(details, value) === false ) { return; } matched.push(`${prop}: ${value}`); } } return matched; } registerScriptlet(matchObjectPropertiesFn, { name: 'match-object-properties.fn', dependencies: [ safeSelf, ], }); /******************************************************************************/ // Reference: // https://github.com/AdguardTeam/Scriptlets/blob/master/wiki/about-scriptlets.md#prevent-xhr // // Added `trusted` argument to allow for returning arbitrary text. Can only // be used through scriptlets requiring trusted source. export function generateContentFn(trusted, directive) { const safe = safeSelf(); const randomize = len => { const chunks = []; let textSize = 0; do { const s = safe.Math_random().toString(36).slice(2); chunks.push(s); textSize += s.length; } while ( textSize < len ); return chunks.join(' ').slice(0, len); }; if ( directive === 'true' ) { return randomize(10); } if ( directive === 'emptyObj' ) { return '{}'; } if ( directive === 'emptyArr' ) { return '[]'; } if ( directive === 'emptyStr' ) { return ''; } if ( directive.startsWith('length:') ) { const match = /^length:(\d+)(?:-(\d+))?$/.exec(directive); if ( match === null ) { return ''; } const min = parseInt(match[1], 10); const extent = safe.Math_max(parseInt(match[2], 10) || 0, min) - min; const len = safe.Math_min(min + extent * safe.Math_random(), 500000); return randomize(len | 0); } if ( directive.startsWith('war:') ) { if ( scriptletGlobals.warOrigin === undefined ) { return ''; } return new Promise(resolve => { const warOrigin = scriptletGlobals.warOrigin; const warName = directive.slice(4); const fullpath = [ warOrigin, '/', warName ]; const warSecret = scriptletGlobals.warSecret; if ( warSecret !== undefined ) { fullpath.push('?secret=', warSecret); } const warXHR = new safe.XMLHttpRequest(); warXHR.responseType = 'text'; warXHR.onloadend = ev => { resolve(ev.target.responseText || ''); }; warXHR.open('GET', fullpath.join('')); warXHR.send(); }).catch(( ) => ''); } if ( trusted ) { return directive; } return ''; } registerScriptlet(generateContentFn, { name: 'generate-content.fn', dependencies: [ safeSelf, ], }); /******************************************************************************/