This commit is contained in:
Raymond Hill 2024-11-30 15:51:38 -05:00
parent d7df6cda4a
commit 0eb7d70b7d
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
9 changed files with 563 additions and 39 deletions

View file

@ -25,7 +25,7 @@
"128": "img/icon_128.png"
},
"manifest_version": 3,
"minimum_chrome_version": "119.0",
"minimum_chrome_version": "122.0",
"name": "__MSG_extName__",
"options_page": "dashboard.html",
"optional_host_permissions": [

View file

@ -234,5 +234,29 @@
"findListsPlaceholder": {
"message": "Find lists",
"description": "Placeholder for the input field used to find lists"
},
"strictblockTitle": {
"message": "Page blocked",
"description": "Webpage title for the strict-blocked page"
},
"strictblockSentence1": {
"message": "uBO Lite has prevented the following page from loading:",
"description": "Sentence used in the strict-blocked page"
},
"strictblockBack": {
"message": "Go back",
"description": "A button to go back to the previous webpage"
},
"strictblockClose": {
"message": "Close this window",
"description": "A button to close the current tab"
},
"strictblockDontWarn": {
"message": "Don't warn me again about this site",
"description": "Label for checkbox in document-blocked page"
},
"strictblockProceed": {
"message": "Proceed",
"description": "A button to navigate to the blocked page"
}
}

View file

@ -0,0 +1,147 @@
/**
uBlock Origin - a browser extension to block requests.
Copyright (C) 2018-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
*/
body {
display: flex;
padding: var(--default-gap-xxlarge) var(--default-gap-small);
justify-content: center;
}
:root.mobile body {
padding: var(--default-gap-small);
}
#rootContainer {
width: min(100%, 640px);
}
#rootContainer > * {
margin: 0 0 var(--default-gap-xxlarge) 0;
}
:root.mobile #rootContainer > * {
margin-bottom: var(--default-gap-xlarge);
}
p {
margin: 0.5em 0;
}
a {
text-decoration: none;
}
.code {
font-size: 13px;
word-break: break-all;
}
#warningSign {
color: var(--accent-surface-1);
fill: var(--accent-surface-1);
font-size: 96px;
line-height: 1;
width: 100%;
}
:root.mobile #warningSign {
font-size: 64px;
}
#theURL {
color: var(--ink-2);
padding: 0;
}
#theURL > * {
margin: 0;
}
#theURL > p {
position: relative;
z-index: 10;
}
#theURL > p > span:first-of-type {
display: block;
max-height: 6lh;
overflow-y: auto;
}
:root.mobile #theURL > p > span:first-of-type {
max-height: 3lh;
}
#theURL #toggleParse {
background-color: transparent;
top: 100%;
box-sizing: border-box;
color: var(--ink-3);
fill: var(--ink-3);
cursor: pointer;
font-size: 1.2rem;
padding: var(--default-gap-xxsmall);
position: absolute;
transform: translate(0, -50%);
}
#theURL:not(.collapsed) #toggleParse > span:first-of-type {
display: none;
}
#theURL.collapsed #toggleParse > span:last-of-type {
display: none;
}
body[dir="ltr"] #toggleParse {
right: 0;
}
body[dir="rtl"] #toggleParse {
left: 0;
}
#theURL > p:hover #toggleParse {
transform: translate(0, -50%) scale(1.15);
}
#parsed {
background-color: var(--surface-1);
border: 4px solid var(--surface-2);
font-size: small;
overflow-x: auto;
padding: var(--default-gap-xxsmall);
text-align: initial;
text-overflow: ellipsis;
}
#theURL.collapsed > #parsed {
display: none;
}
#parsed ul, #parsed li {
list-style-type: none;
}
#parsed li {
white-space: nowrap;
}
#parsed span {
display: inline-block;
}
#parsed span:first-of-type {
font-weight: bold;
}
#actionContainer {
display: flex;
justify-content: space-between;
}
:root.mobile #actionContainer {
justify-content: center;
display: flex;
flex-direction: column;
}
#actionContainer > button {
margin-bottom: 2rem
}
/* Small-screen devices */
:root.mobile button {
width: 100%;
}

View file

@ -23,6 +23,7 @@ import {
browser,
dnr,
i18n,
runtime,
} from './ext.js';
import { fetchJSON } from './fetch.js';
@ -40,6 +41,7 @@ const REDIRECT_REALM_START = REMOVEPARAMS_REALM_END;
const REDIRECT_REALM_END = REDIRECT_REALM_START + RULE_REALM_SIZE;
const MODIFYHEADERS_REALM_START = REDIRECT_REALM_END;
const MODIFYHEADERS_REALM_END = MODIFYHEADERS_REALM_START + RULE_REALM_SIZE;
const STRICTBLOCK_REALM_START = MODIFYHEADERS_REALM_END;
const TRUSTED_DIRECTIVE_BASE_RULE_ID = 8000000;
/******************************************************************************/
@ -60,74 +62,70 @@ function getRulesetDetails() {
/******************************************************************************/
function getDynamicRules() {
if ( getDynamicRules.dynamicRuleMapPromise !== undefined ) {
return getDynamicRules.dynamicRuleMapPromise;
if ( getDynamicRules.promise !== undefined ) {
return getDynamicRules.promise;
}
getDynamicRules.dynamicRuleMapPromise = dnr.getDynamicRules().then(rules => {
getDynamicRules.promise = dnr.getDynamicRules().then(rules => {
const rulesMap = new Map(rules.map(rule => [ rule.id, rule ]));
ubolLog(`Dynamic rule count: ${rulesMap.size}`);
ubolLog(`Available dynamic rule count: ${dnr.MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES - rulesMap.size}`);
return rulesMap;
});
return getDynamicRules.dynamicRuleMapPromise;
return getDynamicRules.promise;
}
function getDynamicRuleIds() {
if ( getDynamicRuleIds.promise !== undefined ) {
return getDynamicRuleIds.promise;
}
getDynamicRuleIds.promise = dnr.getDynamicRules().then(rules => {
return rules.map(rule => rule.id);
});
return getDynamicRuleIds.promise;
}
/******************************************************************************/
async function pruneInvalidRegexRules(realm, rulesIn) {
// Avoid testing already tested regexes
const dynamicRules = await dnr.getDynamicRules();
const validRegexSet = new Set(
dynamicRules.filter(rule =>
rule.condition?.regexFilter && true || false
).map(rule =>
rule.condition.regexFilter
)
);
const rejectedRegexRules = [];
const validateRegex = regex => {
return dnr.isRegexSupported({ regex, isCaseSensitive: false }).then(result => {
const isSupported = result?.isSupported || false;
pruneInvalidRegexRules.validated.set(regex, isSupported);
if ( isSupported ) { return true; }
rejectedRegexRules.push(`\t${regex} ${result?.reason}`);
return false;
});
};
// Validate regex-based rules
const toCheck = [];
const rejectedRegexRules = [];
for ( const rule of rulesIn ) {
if ( rule.condition?.regexFilter === undefined ) {
toCheck.push(true);
continue;
}
const {
regexFilter: regex,
isUrlFilterCaseSensitive: isCaseSensitive
} = rule.condition;
if ( validRegexSet.has(regex) ) {
toCheck.push(true);
const { regexFilter } = rule.condition;
if ( pruneInvalidRegexRules.validated.has(regexFilter) ) {
toCheck.push(pruneInvalidRegexRules.validated.get(regexFilter));
continue;
}
if ( pruneInvalidRegexRules.invalidRegexes.has(regex) ) {
toCheck.push(false);
continue;
}
toCheck.push(
dnr.isRegexSupported({ regex, isCaseSensitive }).then(result => {
if ( result.isSupported ) { return true; }
pruneInvalidRegexRules.invalidRegexes.add(regex);
rejectedRegexRules.push(`\t${regex} ${result.reason}`);
return false;
})
);
toCheck.push(validateRegex(regexFilter));
}
// Collate results
const isValid = await Promise.all(toCheck);
if ( rejectedRegexRules.length !== 0 ) {
ubolLog(
`${realm} realm: rejected regexes:\n`,
ubolLog(`${realm} realm: rejected regexes:\n`,
rejectedRegexRules.join('\n')
);
}
return rulesIn.filter((v, i) => isValid[i]);
}
pruneInvalidRegexRules.invalidRegexes = new Set();
pruneInvalidRegexRules.validated = new Map();
/******************************************************************************/
@ -426,14 +424,88 @@ async function updateModifyHeadersRules() {
/******************************************************************************/
async function updateStrictBlockRules() {
const [
hasOmnipotence,
rulesetDetails,
dynamicRuleMap,
] = await Promise.all([
browser.permissions.contains({ origins: [ '<all_urls>' ] }),
getEnabledRulesetsDetails(),
getDynamicRules(),
]);
// Fetch strick-block hostnames
const toFetch = [];
for ( const details of rulesetDetails ) {
if ( details.rules.strictBlock === 0 ) { continue; }
toFetch.push(fetchJSON(`/rulesets/strict-block/${details.id}`));
}
const strictBlockRulesets = await Promise.all(toFetch);
// Strict-block rules can only be enforced with omnipotence
let toStrictBlock = new Set();
if ( hasOmnipotence ) {
for ( const hostnames of strictBlockRulesets ) {
if ( Array.isArray(hostnames) === false ) { continue; }
toStrictBlock = toStrictBlock.union(new Set(hostnames));
}
}
const addRules = [];
const removeRuleIds = [];
if ( dynamicRuleMap.has(STRICTBLOCK_REALM_START) ) {
removeRuleIds.push(STRICTBLOCK_REALM_START);
dynamicRuleMap.delete(STRICTBLOCK_REALM_START);
}
if ( toStrictBlock.size !== 0 ) {
const ubolOrigin = runtime.getURL('').replace(/\/$/, '');
addRules.push({
id: STRICTBLOCK_REALM_START,
action: {
type: 'redirect',
redirect: {
regexSubstitution: `${ubolOrigin}/strict-block.html#\\0`,
},
},
condition: {
regexFilter: '^.+$',
requestDomains: Array.from(toStrictBlock),
resourceTypes: [ 'main_frame' ],
},
priority: 19,
});
dynamicRuleMap.set(STRICTBLOCK_REALM_START, addRules[0]);
}
if ( addRules.length === 0 && removeRuleIds.length === 0 ) { return; }
if ( removeRuleIds.length !== 0 ) {
ubolLog(`Remove ${removeRuleIds.length} DNR strict-block rules`);
}
if ( addRules.length !== 0 ) {
ubolLog(`Add ${addRules.length} DNR strict-block rules`);
}
return dnr.updateDynamicRules({ addRules, removeRuleIds }).catch(reason => {
console.error(`updateStrictBlockRules() / ${reason}`);
});
}
/******************************************************************************/
// TODO: group all omnipotence-related rules into one realm.
async function updateDynamicRules() {
ubolLog('Called updateDynamicRules()');
return Promise.all([
updateRegexRules(),
updateRemoveparamRules(),
updateRedirectRules(),
updateModifyHeadersRules(),
updateStrictBlockRules(),
]);
}

View file

@ -0,0 +1,207 @@
/*******************************************************************************
uBlock Origin - a comprehensive, efficient content blocker
Copyright (C) 2024-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 { dom, qs$ } from './dom.js';
import { i18n$ } from './i18n.js';
import { sendMessage } from './ext.js';
/******************************************************************************/
const toURL = new URL('about:blank');
function setURL(url) {
try {
toURL.href = url;
} catch(_) {
}
}
setURL(self.location.hash.slice(1));
/******************************************************************************/
const urlToFragment = raw => {
try {
const fragment = new DocumentFragment();
const url = new URL(raw);
const hn = url.hostname;
const i = raw.indexOf(hn);
const b = document.createElement('b');
b.append(hn);
fragment.append(raw.slice(0,i), b, raw.slice(i+hn.length));
return fragment;
} catch(_) {
}
return raw;
};
/******************************************************************************/
dom.clear('#theURL > p > span:first-of-type');
qs$('#theURL > p > span:first-of-type').append(urlToFragment(toURL));
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/691
// Parse URL to extract as much useful information as possible. This is
// useful to assist the user in deciding whether to navigate to the web page.
(( ) => {
if ( typeof URL !== 'function' ) { return; }
const reURL = /^https?:\/\//;
const liFromParam = function(name, value) {
if ( value === '' ) {
value = name;
name = '';
}
const li = dom.create('li');
let span = dom.create('span');
dom.text(span, name);
li.appendChild(span);
if ( name !== '' && value !== '' ) {
li.appendChild(document.createTextNode(' = '));
}
span = dom.create('span');
if ( reURL.test(value) ) {
const a = dom.create('a');
dom.attr(a, 'href', value);
dom.text(a, value);
span.appendChild(a);
} else {
dom.text(span, value);
}
li.appendChild(span);
return li;
};
// https://github.com/uBlockOrigin/uBlock-issues/issues/1649
// Limit recursion.
const renderParams = function(parentNode, rawURL, depth = 0) {
let url;
try {
url = new URL(rawURL);
} catch(ex) {
return false;
}
const search = url.search.slice(1);
if ( search === '' ) { return false; }
url.search = '';
const li = liFromParam(i18n$('docblockedNoParamsPrompt'), url.href);
parentNode.appendChild(li);
const params = new self.URLSearchParams(search);
for ( const [ name, value ] of params ) {
const li = liFromParam(name, value);
if ( depth < 2 && reURL.test(value) ) {
const ul = dom.create('ul');
renderParams(ul, value, depth + 1);
li.appendChild(ul);
}
parentNode.appendChild(li);
}
return true;
};
if ( renderParams(qs$('#parsed'), toURL) === false ) { return; }
dom.cl.remove('#toggleParse', 'hidden');
dom.on('#toggleParse', 'click', ( ) => {
dom.cl.toggle('#theURL', 'collapsed');
vAPI.localStorage.setItem(
'document-blocked-expand-url',
(dom.cl.has('#theURL', 'collapsed') === false).toString()
);
});
vAPI.localStorage.getItemAsync('document-blocked-expand-url').then(value => {
dom.cl.toggle('#theURL', 'collapsed', value !== 'true' && value !== true);
});
})();
/******************************************************************************/
// https://www.reddit.com/r/uBlockOrigin/comments/breeux/close_this_window_doesnt_work_on_firefox/
if ( window.history.length > 1 ) {
dom.on('#back', 'click', ( ) => {
window.history.back();
});
qs$('#bye').style.display = 'none';
} else {
dom.on('#bye', 'click', ( ) => {
sendMessage({
what: 'closeThisTab',
});
});
qs$('#back').style.display = 'none';
}
/******************************************************************************/
const getTargetHostname = function() {
const url = new URL(toURL);
return url.hostname;
};
const proceedToURL = function() {
window.location.replace(toURL);
};
const proceedTemporary = async function() {
await sendMessage({
what: 'temporarilyWhitelistDocument',
hostname: getTargetHostname(),
});
proceedToURL();
};
const proceedPermanent = async function() {
await sendMessage({
what: 'toggleHostnameSwitch',
name: 'no-strict-blocking',
hostname: getTargetHostname(),
deep: true,
state: true,
persist: true,
});
proceedToURL();
};
dom.on('#disableWarning', 'change', ev => {
const checked = ev.target.checked;
dom.cl.toggle('[data-i18n="docblockedBack"]', 'disabled', checked);
dom.cl.toggle('[data-i18n="docblockedClose"]', 'disabled', checked);
});
dom.on('#proceed', 'click', ( ) => {
if ( qs$('#disableWarning').checked ) {
proceedPermanent();
} else {
proceedTemporary();
}
});
/******************************************************************************/

View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=yes">
<title data-i18n="strictblockTitle"></title>
<link rel="stylesheet" href="css/default.css">
<link rel="stylesheet" href="css/common.css">
<link rel="stylesheet" href="css/fa-icons.css">
<link rel="stylesheet" href="css/strict-block.css">
<link rel="shortcut icon" type="image/png" href="img/icon_64.png"/>
</head>
<body>
<div id="rootContainer">
<div id="warningSign">
<a class="fa-icon" href="https://github.com/gorhill/uBlock/wiki/Strict-blocking" target="_blank" rel="noopener noreferrer">exclamation-triangle</a>
</div>
<div>
<p data-i18n="strictblockSentence1">_</p>
<div id="theURL" class="collapsed">
<p class="code"><span>&nbsp;</span><span id="toggleParse" class="hidden"><span class="fa-icon">zoom-in</span><span class="fa-icon">zoom-out</span></span></p>
<ul id="parsed"></ul>
</div>
</div>
<div class="li">
<label><span class="input checkbox"><input type="checkbox" id="disableWarning"><svg viewBox="0 0 24 24"><path d="M1.73,12.91 8.1,19.28 22.79,4.59"/></svg></span><span data-i18n="strictblockDontWarn">_</span></label>
</div>
<div id="actionContainer">
<button id="back" data-i18n="strictblockBack" type="button">_<span class="hover"></span></button>
<button id="bye" data-i18n="strictblockClose" type="button">_<span class="hover"></span></button>
<button id="proceed" class="preferred" data-i18n="strictblockProceed" type="button"><span class="hover"></span></button>
</div>
</div>
<script src="js/theme.js" type="module"></script>
<script src="js/fa-icons.js" type="module"></script>
<script src="js/i18n.js" type="module"></script>
<script src="js/strict-block.js" type="module"></script>
</body>
</html>

View file

@ -16,10 +16,10 @@
"browser_specific_settings": {
"gecko": {
"id": "uBOLite@raymondhill.net",
"strict_min_version": "114.0"
"strict_min_version": "127.0"
},
"gecko_android": {
"strict_min_version": "114.0"
"strict_min_version": "127.0"
}
},
"declarative_net_request": {

View file

@ -433,6 +433,33 @@ async function processNetworkFilters(assetDetails, network) {
);
}
const strictBlocked = new Set();
for ( const rule of plainGood ) {
if ( rule.action.type !== 'block' ) { continue; }
if ( rule.condition.domainType ) { continue; }
if ( rule.condition.regexFilter ) { continue; }
if ( rule.condition.urlFilter ) { continue; }
if ( rule.condition.requestMethods ) { continue; }
if ( rule.condition.excludedRequestMethods ) { continue; }
if ( rule.condition.resourceTypes ) { continue; }
if ( rule.condition.excludedResourceTypes ) { continue; }
if ( rule.condition.responseHeaders ) { continue; }
if ( rule.condition.excludedResponseHeaders ) { continue; }
if ( rule.condition.initiatorDomains ) { continue; }
if ( rule.condition.excludedInitiatorDomains ) { continue; }
if ( rule.condition.requestDomains === undefined ) { continue; }
if ( rule.condition.excludedRequestDomains ) { continue; }
for ( const hn of rule.condition.requestDomains ) {
strictBlocked.add(hn);
}
}
if ( strictBlocked.size !== 0 ) {
writeFile(
`${rulesetDir}/strict-block/${assetDetails.id}.json`,
toJSONRuleset(Array.from(strictBlocked))
);
}
return {
total: rules.length,
plain: plainGood.length,
@ -442,6 +469,7 @@ async function processNetworkFilters(assetDetails, network) {
removeparam: removeparamsGood.length,
redirect: redirects.length,
modifyHeaders: modifyHeaders.length,
strictBlock: strictBlocked.size,
};
}
@ -1085,6 +1113,7 @@ async function rulesetFromURLs(assetDetails) {
removeparam: netStats.removeparam,
redirect: netStats.redirect,
modifyHeaders: netStats.modifyHeaders,
strictBlock: netStats.strictBlock,
discarded: netStats.discarded,
rejected: netStats.rejected,
},

View file

@ -47,10 +47,12 @@
</div>
</div>
<div id="templates" style="display: none;">
<ul>
<li class="filterList">
<a class="filterListSource" href="asset-viewer.html?url=" target="_blank"></a>&nbsp;<!--
--><a class="fa-icon filterListSupport hidden" href="#" target="_blank" rel="noopener noreferrer">home</a>
</span>
</li>
</ul>
</div>
<script src="lib/hsluv/hsluv-0.1.0.min.js"></script>