From b73bf5d4ca390c3e32332b51c2b81a6b938aa1e8 Mon Sep 17 00:00:00 2001 From: libroot Date: Sun, 11 Oct 2026 21:39:08 +0000 Subject: [PATCH] Add Android support + now don't block the whole page if a sub-resource is served via CF --- README | 1 + background.js | 108 ++++++++++++++++++++++++-------------------------- blocked.html | 28 ++++++++----- blocked.js | 6 ++- manifest.json | 16 ++++++-- popup.html | 47 +++++++++++++--------- popup.js | 42 +++++++------------- 7 files changed, 133 insertions(+), 115 deletions(-) diff --git a/README b/README index ac1cadf..6810471 100644 --- a/README +++ b/README @@ -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/) diff --git a/background.js b/background.js index 52a55b7..de3b322 100644 --- a/background.js +++ b/background.js @@ -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 }; - } - - // 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-') - // ); - - const cf_header = details.responseHeaders.find(header => - cloudflare_headers.includes(header.name.toLowerCase()) - ); +function blockedPageUrl(header, url) { + const query = new URLSearchParams({ + name: header.name, + value: header.value || '', + url: url + }).toString(); + return browser.runtime.getURL('blocked.html') + '?' + query; +} - if (cf_header) { - console.log('Cloudflare header found:', cf_header); - - const query_params = new URLSearchParams({ - name: cf_header.name, - value: cf_header.value, - url: details.url - }).toString(); +function checkResponse(details) { + if (!blockingEnabled) { + return {}; + } - /* - * 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! - */ + const cfHeader = (details.responseHeaders || []).find(header => + CLOUDFLARE_HEADERS.has(header.name.toLowerCase()) + ); + if (!cfHeader) { + return {}; + } - // 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 }); + if (details.type === 'main_frame') { + return { redirectUrl: blockedPageUrl(cfHeader, details.url) }; + } - // Block the request, reject Cloudflare! - return { cancel: true }; - } - }, + // 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 }; +} + +browser.webRequest.onHeadersReceived.addListener( + details => stateLoaded + ? checkResponse(details) + : stateReady.then(() => checkResponse(details)), { urls: [''] }, ['blocking', 'responseHeaders'] ); diff --git a/blocked.html b/blocked.html index 0489208..9041b83 100644 --- a/blocked.html +++ b/blocked.html @@ -5,16 +5,27 @@ Cloudflare page blocked @@ -22,18 +33,17 @@

This page has been blocked by the Cloudflare Blocker extension due to a Cloudflare header.

-

+

Blocked Header:
Loading...
-

+
-

+

Blocked response URL:
Loading...
-

+
-
-

You can probably view the page on the Wayback Machine.

+

You can probably view the page on the Wayback Machine.

diff --git a/blocked.js b/blocked.js index b31eead..b706467 100644 --- a/blocked.js +++ b/blocked.js @@ -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; } }); diff --git a/manifest.json b/manifest.json index 35ee1c9..6540ee8 100644 --- a/manifest.json +++ b/manifest.json @@ -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", "" ], "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" diff --git a/popup.html b/popup.html index 5b31137..12f57cb 100644 --- a/popup.html +++ b/popup.html @@ -5,49 +5,60 @@ Cloudflare Blocker

Cloudflare Blocker

-
-
-
+

Blocking is ON

- + diff --git a/popup.js b/popup.js index 0bf0c0c..2c66758 100644 --- a/popup.js +++ b/popup.js @@ -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 - }); -}); -