Add Android support + now don't block the whole page if a sub-resource is served via CF

This commit is contained in:
libroot 2026-10-11 21:39:08 +00:00
commit b73bf5d4ca
7 changed files with 132 additions and 114 deletions

1
README
View file

@ -3,5 +3,6 @@
* Detects Cloudflare-related headers in outgoing requests.
* Blocks the request if Cloudflare is present.
* Toggle the blocker on/off via the extension popup.
* Android support added in version 1.1.0
Available from Mozilla Add-ons: [https://addons.mozilla.org/en-US/firefox/addon/cloudflare-blocker/](https://addons.mozilla.org/en-US/firefox/addon/cloudflare-blocker/)

View file

@ -1,69 +1,65 @@
// These are Cloudflare's headers, taken from here:
// https://developers.cloudflare.com/fundamentals/reference/http-headers/
const CLOUDFLARE_HEADERS = new Set([
'cf-connecting-ip',
'cf-connecting-ipv6',
'cf-ew-via',
'cf-pseudo-ipv4',
'cf-ray',
'cf-ipcountry',
'cf-visitor',
'cf-worker'
]);
let blockingEnabled = true;
let stateLoaded = false;
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'toggleBlocking') {
blockingEnabled = message.enabled;
}
const stateReady = browser.storage.local.get({ blockingEnabled: true })
.then(result => { blockingEnabled = result.blockingEnabled; })
.catch(() => { /* keep the default */ })
.then(() => { stateLoaded = true; });
if (message.action === 'getBlockingState') {
sendResponse({ action: 'setBlockingState', enabled: blockingEnabled });
browser.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.blockingEnabled) {
blockingEnabled = changes.blockingEnabled.newValue;
}
});
chrome.webRequest.onHeadersReceived.addListener(
function(details) {
if (!blockingEnabled) {
return { cancel: false };
}
function blockedPageUrl(header, url) {
const query = new URLSearchParams({
name: header.name,
value: header.value || '',
url: url
}).toString();
return browser.runtime.getURL('blocked.html') + '?' + query;
}
// These are Cloudflare's headers, taken from here:
// https://developers.cloudflare.com/fundamentals/reference/http-headers/
const cloudflare_headers = [
'cf-connecting-ip',
'cf-connecting-ipv6',
'cf-ew-via',
'cf-Pseudo-ipv4',
'cf-ray',
'cf-ipcountry',
'cf-visitor',
'cf-worker'
];
// You might want capture all headers prefixed with "cf-":
// const cf_header = details.responseHeaders.find(header =>
// header.name.toLowerCase().startsWith('cf-')
// );
function checkResponse(details) {
if (!blockingEnabled) {
return {};
}
const cf_header = details.responseHeaders.find(header =>
cloudflare_headers.includes(header.name.toLowerCase())
);
const cfHeader = (details.responseHeaders || []).find(header =>
CLOUDFLARE_HEADERS.has(header.name.toLowerCase())
);
if (!cfHeader) {
return {};
}
if (cf_header) {
console.log('Cloudflare header found:', cf_header);
if (details.type === 'main_frame') {
return { redirectUrl: blockedPageUrl(cfHeader, details.url) };
}
const query_params = new URLSearchParams({
name: cf_header.name,
value: cf_header.value,
url: details.url
}).toString();
// Subresource (image, script, stylesheet, font, iframe, fetch...): drop
// just that request. The page itself keeps loading without it.
// Makes the extension a bit more usable.
return { cancel: true };
}
/*
* It would be cool to pass the tab URL in the query params, but how?
* When accessing chrome.tabs here, the active tab URL is "about:newtab".
* Because we block the request immediately and the tab URL state doesn't
* never update to the URL we tried to access. I'd like to pass the URL
* to the web.archive.org/tab_url_here in blocked.html.
* Email contact@libroot.org if you know a solution!
*/
// Redirect to our custom blocked.html page
const url = chrome.runtime.getURL('blocked.html') + '?' + query_params;
console.log('Redirecting to blocked page:', url);
chrome.tabs.update(details.tabId, { url: url });
// Block the request, reject Cloudflare!
return { cancel: true };
}
},
browser.webRequest.onHeadersReceived.addListener(
details => stateLoaded
? checkResponse(details)
: stateReady.then(() => checkResponse(details)),
{ urls: ['<all_urls>'] },
['blocking', 'responseHeaders']
);

View file

@ -5,16 +5,27 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cloudflare page blocked</title>
<style>
:root {
color-scheme: light dark;
}
body {
font-family: Arial, sans-serif;
padding: 20px;
font-family: system-ui, Arial, sans-serif;
padding: 16px;
text-align: center;
max-width: 40em;
margin: 0 auto;
}
h1 {
color: red;
font-size: 1.4em;
}
pre {
white-space: pre-wrap;
overflow-wrap: anywhere;
text-align: left;
}
a {
color: blue;
color: #0060df;
text-decoration: none;
}
</style>
@ -22,18 +33,17 @@
<body>
<h1>This page has been blocked by the Cloudflare Blocker extension due to a Cloudflare header.</h1>
<p>
<div>
<strong>Blocked Header:</strong>
<pre id="header_info">Loading...</pre>
</p>
</div>
<p>
<div>
<strong>Blocked response URL:</strong>
<pre id="url">Loading...</pre>
</p>
</div>
<br>
<p>You can probably view the page on the <a href="https://web.archive.org" target="_blank">Wayback Machine</a>.</p>
<p>You can probably view the page on the <a id="wayback" href="https://web.archive.org" target="_blank" rel="noopener">Wayback Machine</a>.</p>
<script src="blocked.js"></script>
</body>

View file

@ -10,12 +10,14 @@ function getUrlParams() {
document.addEventListener('DOMContentLoaded', function() {
const params = getUrlParams();
if (params.name && params.value) {
const header_info = `Name: ${params.name}\nValue: ${params.value}`;
if (params.name) {
const header_info = `Name: ${params.name}\nValue: ${params.value || ''}`;
document.getElementById('header_info').textContent = header_info;
}
if (params.url) {
document.getElementById('url').textContent = params.url;
document.getElementById('wayback').href =
'https://web.archive.org/web/' + params.url;
}
});

View file

@ -1,6 +1,6 @@
{
"name": "Cloudflare Blocker",
"version": "1.0.0",
"version": "1.1.0",
"author": "libroot.org",
"developer": {
"name": "libroot",
@ -8,10 +8,19 @@
},
"description": "Blocks requests if they have Cloudflare's headers, and shows a blocked page.",
"manifest_version": 2,
"browser_specific_settings": {
"gecko": {
"id": "cloudflare-blocker@libroot.org",
"strict_min_version": "115.0"
},
"gecko_android": {
"strict_min_version": "120.0"
}
},
"permissions": [
"webRequest",
"webRequestBlocking",
"tabs",
"storage",
"<all_urls>"
],
"background": {
@ -20,7 +29,8 @@
},
"browser_action": {
"default_popup": "popup.html",
"default_icon": "icons/icon.png"
"default_icon": "icons/icon.png",
"default_title": "Cloudflare Blocker"
},
"web_accessible_resources": [
"blocked.html"

View file

@ -5,47 +5,58 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cloudflare Blocker</title>
<style>
:root {
color-scheme: light dark;
}
body {
font-family: Arial, sans-serif;
padding: 10px;
width: 200px;
font-family: system-ui, Arial, sans-serif;
padding: 16px;
min-width: 200px;
margin: 0;
}
h3 {
margin-top: 0;
}
#toggle {
width: 50px;
height: 25px;
appearance: none;
border: 0;
padding: 0;
width: 72px;
height: 40px;
position: relative;
border-radius: 15px;
border-radius: 20px;
background-color: #ccc;
cursor: pointer;
margin: 4px 0;
}
#toggle .slider {
width: 25px;
height: 25px;
width: 32px;
height: 32px;
border-radius: 50%;
background-color: white;
position: absolute;
top: 0;
transition: 0.3s;
top: 4px;
left: 4px;
transition: left 0.2s;
}
#toggle.active {
background-color: #4CAF50;
}
#toggle.active .slider {
left: 25px;
left: 36px;
}
#status {
margin-top: 10px;
font-size: 14px;
color: #333;
margin-top: 12px;
font-size: 16px;
}
</style>
</head>
<body>
<h3>Cloudflare Blocker</h3>
<div id="toggle" class="active">
<div class="slider"></div>
</div>
<button id="toggle" class="active" role="switch" aria-checked="true" aria-label="Block Cloudflare sites">
<span class="slider"></span>
</button>
<p id="status">Blocking is ON</p>
<script src="popup.js"></script>

View file

@ -1,32 +1,20 @@
const toggle = document.getElementById('toggle');
const status = document.getElementById('status');
chrome.runtime.sendMessage({ action: 'getBlockingState' }, (response) => {
if (response && response.enabled !== undefined) {
const blockingEnabled = response.enabled;
function render(enabled) {
toggle.classList.toggle('active', enabled);
toggle.setAttribute('aria-checked', String(enabled));
status.textContent = enabled ? 'Blocking is ON' : 'Blocking is OFF';
}
if (blockingEnabled) {
toggle.classList.add('active');
status.textContent = 'Blocking is ON';
} else {
toggle.classList.remove('active');
status.textContent = 'Blocking is OFF';
}
}
// read and write the state straight from storage, so the toggle works even
// if the background page is currently unloaded
browser.storage.local.get({ blockingEnabled: true })
.then(result => render(result.blockingEnabled))
.catch(() => render(true));
toggle.addEventListener('click', () => {
const enabled = !toggle.classList.contains('active');
render(enabled);
browser.storage.local.set({ blockingEnabled: enabled });
});
toggle.addEventListener('click', function () {
const isActive = toggle.classList.toggle('active');
if (isActive) {
status.textContent = 'Blocking is ON';
} else {
status.textContent = 'Blocking is OFF';
}
chrome.runtime.sendMessage({
action: 'toggleBlocking',
enabled: isActive
});
});