Add Android support + now don't block the whole page if a sub-resource is served via CF
This commit is contained in:
parent
e90ff542b0
commit
b73bf5d4ca
7 changed files with 132 additions and 114 deletions
1
README
1
README
|
|
@ -3,5 +3,6 @@
|
||||||
* Detects Cloudflare-related headers in outgoing requests.
|
* Detects Cloudflare-related headers in outgoing requests.
|
||||||
* Blocks the request if Cloudflare is present.
|
* Blocks the request if Cloudflare is present.
|
||||||
* Toggle the blocker on/off via the extension popup.
|
* 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/)
|
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/)
|
||||||
|
|
|
||||||
|
|
@ -1,69 +1,65 @@
|
||||||
let blockingEnabled = true;
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
||||||
if (message.action === 'toggleBlocking') {
|
|
||||||
blockingEnabled = message.enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.action === 'getBlockingState') {
|
|
||||||
sendResponse({ action: 'setBlockingState', enabled: blockingEnabled });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
chrome.webRequest.onHeadersReceived.addListener(
|
|
||||||
function(details) {
|
|
||||||
if (!blockingEnabled) {
|
|
||||||
return { cancel: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are Cloudflare's headers, taken from here:
|
// These are Cloudflare's headers, taken from here:
|
||||||
// https://developers.cloudflare.com/fundamentals/reference/http-headers/
|
// https://developers.cloudflare.com/fundamentals/reference/http-headers/
|
||||||
const cloudflare_headers = [
|
const CLOUDFLARE_HEADERS = new Set([
|
||||||
'cf-connecting-ip',
|
'cf-connecting-ip',
|
||||||
'cf-connecting-ipv6',
|
'cf-connecting-ipv6',
|
||||||
'cf-ew-via',
|
'cf-ew-via',
|
||||||
'cf-Pseudo-ipv4',
|
'cf-pseudo-ipv4',
|
||||||
'cf-ray',
|
'cf-ray',
|
||||||
'cf-ipcountry',
|
'cf-ipcountry',
|
||||||
'cf-visitor',
|
'cf-visitor',
|
||||||
'cf-worker'
|
'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 =>
|
let blockingEnabled = true;
|
||||||
cloudflare_headers.includes(header.name.toLowerCase())
|
let stateLoaded = false;
|
||||||
);
|
|
||||||
|
|
||||||
if (cf_header) {
|
const stateReady = browser.storage.local.get({ blockingEnabled: true })
|
||||||
console.log('Cloudflare header found:', cf_header);
|
.then(result => { blockingEnabled = result.blockingEnabled; })
|
||||||
|
.catch(() => { /* keep the default */ })
|
||||||
|
.then(() => { stateLoaded = true; });
|
||||||
|
|
||||||
const query_params = new URLSearchParams({
|
browser.storage.onChanged.addListener((changes, area) => {
|
||||||
name: cf_header.name,
|
if (area === 'local' && changes.blockingEnabled) {
|
||||||
value: cf_header.value,
|
blockingEnabled = changes.blockingEnabled.newValue;
|
||||||
url: details.url
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function blockedPageUrl(header, url) {
|
||||||
|
const query = new URLSearchParams({
|
||||||
|
name: header.name,
|
||||||
|
value: header.value || '',
|
||||||
|
url: url
|
||||||
}).toString();
|
}).toString();
|
||||||
|
return browser.runtime.getURL('blocked.html') + '?' + query;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
function checkResponse(details) {
|
||||||
* It would be cool to pass the tab URL in the query params, but how?
|
if (!blockingEnabled) {
|
||||||
* When accessing chrome.tabs here, the active tab URL is "about:newtab".
|
return {};
|
||||||
* 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 cfHeader = (details.responseHeaders || []).find(header =>
|
||||||
const url = chrome.runtime.getURL('blocked.html') + '?' + query_params;
|
CLOUDFLARE_HEADERS.has(header.name.toLowerCase())
|
||||||
console.log('Redirecting to blocked page:', url);
|
);
|
||||||
chrome.tabs.update(details.tabId, { url: url });
|
if (!cfHeader) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// Block the request, reject Cloudflare!
|
if (details.type === 'main_frame') {
|
||||||
|
return { redirectUrl: blockedPageUrl(cfHeader, details.url) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 };
|
return { cancel: true };
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
browser.webRequest.onHeadersReceived.addListener(
|
||||||
|
details => stateLoaded
|
||||||
|
? checkResponse(details)
|
||||||
|
: stateReady.then(() => checkResponse(details)),
|
||||||
{ urls: ['<all_urls>'] },
|
{ urls: ['<all_urls>'] },
|
||||||
['blocking', 'responseHeaders']
|
['blocking', 'responseHeaders']
|
||||||
);
|
);
|
||||||
|
|
|
||||||
28
blocked.html
28
blocked.html
|
|
@ -5,16 +5,27 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Cloudflare page blocked</title>
|
<title>Cloudflare page blocked</title>
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: system-ui, Arial, sans-serif;
|
||||||
padding: 20px;
|
padding: 16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
max-width: 40em;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
h1 {
|
h1 {
|
||||||
color: red;
|
color: red;
|
||||||
|
font-size: 1.4em;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: blue;
|
color: #0060df;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -22,18 +33,17 @@
|
||||||
<body>
|
<body>
|
||||||
<h1>This page has been blocked by the Cloudflare Blocker extension due to a Cloudflare header.</h1>
|
<h1>This page has been blocked by the Cloudflare Blocker extension due to a Cloudflare header.</h1>
|
||||||
|
|
||||||
<p>
|
<div>
|
||||||
<strong>Blocked Header:</strong>
|
<strong>Blocked Header:</strong>
|
||||||
<pre id="header_info">Loading...</pre>
|
<pre id="header_info">Loading...</pre>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<p>
|
<div>
|
||||||
<strong>Blocked response URL:</strong>
|
<strong>Blocked response URL:</strong>
|
||||||
<pre id="url">Loading...</pre>
|
<pre id="url">Loading...</pre>
|
||||||
</p>
|
</div>
|
||||||
|
|
||||||
<br>
|
<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>
|
||||||
<p>You can probably view the page on the <a href="https://web.archive.org" target="_blank">Wayback Machine</a>.</p>
|
|
||||||
|
|
||||||
<script src="blocked.js"></script>
|
<script src="blocked.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ function getUrlParams() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const params = getUrlParams();
|
const params = getUrlParams();
|
||||||
|
|
||||||
if (params.name && params.value) {
|
if (params.name) {
|
||||||
const header_info = `Name: ${params.name}\nValue: ${params.value}`;
|
const header_info = `Name: ${params.name}\nValue: ${params.value || ''}`;
|
||||||
document.getElementById('header_info').textContent = header_info;
|
document.getElementById('header_info').textContent = header_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.url) {
|
if (params.url) {
|
||||||
document.getElementById('url').textContent = params.url;
|
document.getElementById('url').textContent = params.url;
|
||||||
|
document.getElementById('wayback').href =
|
||||||
|
'https://web.archive.org/web/' + params.url;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "Cloudflare Blocker",
|
"name": "Cloudflare Blocker",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"author": "libroot.org",
|
"author": "libroot.org",
|
||||||
"developer": {
|
"developer": {
|
||||||
"name": "libroot",
|
"name": "libroot",
|
||||||
|
|
@ -8,10 +8,19 @@
|
||||||
},
|
},
|
||||||
"description": "Blocks requests if they have Cloudflare's headers, and shows a blocked page.",
|
"description": "Blocks requests if they have Cloudflare's headers, and shows a blocked page.",
|
||||||
"manifest_version": 2,
|
"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": [
|
"permissions": [
|
||||||
"webRequest",
|
"webRequest",
|
||||||
"webRequestBlocking",
|
"webRequestBlocking",
|
||||||
"tabs",
|
"storage",
|
||||||
"<all_urls>"
|
"<all_urls>"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
|
|
@ -20,7 +29,8 @@
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_popup": "popup.html",
|
"default_popup": "popup.html",
|
||||||
"default_icon": "icons/icon.png"
|
"default_icon": "icons/icon.png",
|
||||||
|
"default_title": "Cloudflare Blocker"
|
||||||
},
|
},
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
"blocked.html"
|
"blocked.html"
|
||||||
|
|
|
||||||
45
popup.html
45
popup.html
|
|
@ -5,47 +5,58 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Cloudflare Blocker</title>
|
<title>Cloudflare Blocker</title>
|
||||||
<style>
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: system-ui, Arial, sans-serif;
|
||||||
padding: 10px;
|
padding: 16px;
|
||||||
width: 200px;
|
min-width: 200px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
#toggle {
|
#toggle {
|
||||||
width: 50px;
|
appearance: none;
|
||||||
height: 25px;
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 72px;
|
||||||
|
height: 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 15px;
|
border-radius: 20px;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
#toggle .slider {
|
#toggle .slider {
|
||||||
width: 25px;
|
width: 32px;
|
||||||
height: 25px;
|
height: 32px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 4px;
|
||||||
transition: 0.3s;
|
left: 4px;
|
||||||
|
transition: left 0.2s;
|
||||||
}
|
}
|
||||||
#toggle.active {
|
#toggle.active {
|
||||||
background-color: #4CAF50;
|
background-color: #4CAF50;
|
||||||
}
|
}
|
||||||
#toggle.active .slider {
|
#toggle.active .slider {
|
||||||
left: 25px;
|
left: 36px;
|
||||||
}
|
}
|
||||||
#status {
|
#status {
|
||||||
margin-top: 10px;
|
margin-top: 12px;
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h3>Cloudflare Blocker</h3>
|
<h3>Cloudflare Blocker</h3>
|
||||||
<div id="toggle" class="active">
|
<button id="toggle" class="active" role="switch" aria-checked="true" aria-label="Block Cloudflare sites">
|
||||||
<div class="slider"></div>
|
<span class="slider"></span>
|
||||||
</div>
|
</button>
|
||||||
<p id="status">Blocking is ON</p>
|
<p id="status">Blocking is ON</p>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
|
|
||||||
|
|
|
||||||
40
popup.js
40
popup.js
|
|
@ -1,32 +1,20 @@
|
||||||
const toggle = document.getElementById('toggle');
|
const toggle = document.getElementById('toggle');
|
||||||
const status = document.getElementById('status');
|
const status = document.getElementById('status');
|
||||||
|
|
||||||
chrome.runtime.sendMessage({ action: 'getBlockingState' }, (response) => {
|
function render(enabled) {
|
||||||
if (response && response.enabled !== undefined) {
|
toggle.classList.toggle('active', enabled);
|
||||||
const blockingEnabled = response.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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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({
|
// read and write the state straight from storage, so the toggle works even
|
||||||
action: 'toggleBlocking',
|
// if the background page is currently unloaded
|
||||||
enabled: isActive
|
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 });
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue