Initial commit

This commit is contained in:
MassiveBox 2024-01-30 15:49:24 +01:00
commit ef2f7aeaaa
Signed by: massivebox
GPG key ID: 9B74D3A59181947D
22 changed files with 566 additions and 0 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 traktofon, (c) 2024 MassiveBox.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

53
README.md Normal file
View file

@ -0,0 +1,53 @@
Detect CloudFlare (Chromium port)
===============================
An extension for Chromium-based browsers which aims to detect whether the current page uses CloudFlare.
It adds an icon to the browser toolbar which indicates the detection status.
This is intended to help users detect which of the sites they visit is using CloudFlare, so that they can take the appropriate actions (e.g. changing their password) if they are worried that they are affected by the [cloudbleed bug](https://bugs.chromium.org/p/project-zero/issues/detail?id=1139).
**Note**: This extension detects whether the page _currently_ uses CloudFlare. It cannot detect whether the page might have used CloudFlare in the past, especially during the time period where it was affected by cloudbleed (i.e. 2016-09-22 thru 2017-02-18). For this, you may check [this list](https://github.com/pirate/sites-using-cloudflare).
Installation
------------
The extension is not available (yet) on Chrome's WebStore. To install the extension from source:
* Clone or download this repository
* In your browser, go to [chrome://extensions](chrome://extensions)
* Enable "developer mode" - there should be a toggle on the top right for it
* Click "load unpacked extension" and choose the folder where you cloned or downloaded the repository
* The extension will be loaded. Note that it won't have automatic updates. You might have to pin it to keep it always visible.
How it Works
------------
The extension analyzes all HTTP(S) requests and checks whether any of them are served by CloudFlare proxies, as identified by certain headers in the HTTP(S) response. Based on the result of this analysis, the icon changes color:
![green](icons/cf-green-32.png) No requests were served by CloudFlare.
![orange](icons/cf-orange-32.png) Extenal resources were served by CloudFlare.
![red](icons/cf-red-32.png) The page itself was served by CloudFlare.
Clicking on the icon will show a popup with detailed information about the domains which use CloudFlare.
Because the extension only uses the headers provided by the server to determine whether a website uses CloudFlare or not, it doesn't make any additional connection to other servers, APIs or DNS resolvers.
Notes
-----
* This extension analyzes **all** requests made by the browser, though other extensions (e.g. adblockers) may block some requests before they are made.
* When navigating forward/backward inside a tab, the detection status will reset to neutral. To get the correct status, the page needs to be reloaded. (Though nowadays the reload happens automatically for many webpages.)
## Fork information and donations
This is a fork of [cf-detect](https://github.com/traktofon/cf-detect), which is for Firefox-based browsers. Thanks to [traktofon](https://github.com/traktofon) for the base code.
Right now, the extension is a simple port of the original, with the same features being supported. I plan on working more on this project to port it to Manifest V3 and add some improvements.
Copyright (c) 2017 traktofon, (c) 2024 MassiveBox.
The software is distributed under the MIT license. Consult the LICENSE file to learn more.
If you like this fork, consider supporting its creator with a small [donation](https://massivebox.net/pages/donate.html).

189
background.js Normal file
View file

@ -0,0 +1,189 @@
const iconColorAndDesc = {
0: { color: "grey", desc: "Detect Cloudflare" },
1: { color: "green", desc: "This page doesn't seem to use Cloudflare." },
2: { color: "orange", desc: "External resources on this page use Cloudflare." },
3: { color: "red", desc: "This page uses Cloudflare!" }
};
function Counter() {
this.counts = new Map();
this.setCount = function(key,val) {
this.counts.set(key, val);
};
this.getCount = function(key) {
if (!this.counts.has(key)) { return 0; }
else { return this.counts.get(key); }
};
this.incCount = function(key) {
if (!this.counts.has(key)) { this.counts.set(key,1); }
else { this.counts.set(key, this.counts.get(key)+1); }
};
this.delCount = function(key) {
this.counts.delete(key);
};
}
function mapToObject( map ) {
obj = {};
map.forEach( function(val,key) { obj[key]=val; } );
return obj;
}
function CFInfo() {
this.domainCounter = new Counter();
this.result = 0;
// maybe more in the future
}
function CFInfoByTab() {
this.info = new Map();
this.getInfo = function(id) {
return this.info.get(id);
}
this.getOrCreate = function(id) {
if (!this.info.has(id)) { this.info.set(id, new CFInfo()); }
return this.info.get(id);
};
this.delInfo = function(id) {
this.info.delete(id);
};
}
var cfInfo = new CFInfoByTab();
function onError(e) {
console.log(`CF-Detect-Background: ${e}`);
}
function getDomainFromURL( urltxt ) {
try {
var url = new URL(urltxt);
return url.hostname;
} catch(err) {
return null;
}
}
function updateStatus( tabId ) {
var info = cfInfo.getInfo(tabId);
if (info) {
if (info.result >= 3) return; // no need for further updates
var counts = info.domainCounter.counts;
if (counts.size == 0) {
info.result = 1;
updateIcon( tabId, 1 );
} else {
chrome.tabs.get(tabId, function(tab) {
try {
var domain = getDomainFromURL(tab.url);
if (counts.has(domain)) {
info.result = 3;
updateIcon( tabId, 3 );
} else {
info.result = 2;
updateIcon( tabId, 2 );
}
} catch (error) {
onError(error);
}
});
}
} else {
updateIcon( tabId, 0 );
}
}
function updateIcon( tabId, result ) {
var cd = iconColorAndDesc[result];
var color = cd.color;
var title = cd.desc;
chrome.browserAction.setTitle({
tabId: tabId,
title: title
});
chrome.browserAction.setIcon({
tabId: tabId,
path: {
16: `icons/cf-${color}-16.png` ,
32: `icons/cf-${color}-32.png` ,
64: `icons/cf-${color}-64.png`
}
});
}
function cfdetect( details ) {
var headers = details.responseHeaders;
var cf = false;
for (var i=0; i<headers.length; i++) {
var h = headers[i];
var hname = h.name.toLowerCase();
if ((hname === "cf-ray") ||
(hname === "server" && h.value === "cloudflare-nginx")) {
cf = true;
break;
}
}
var tabId = details.tabId;
if (tabId == -1) return;
var info = cfInfo.getOrCreate(tabId);
if (cf) {
var ctr = info.domainCounter;
var domain = getDomainFromURL( details.url );
ctr.incCount(domain);
}
updateStatus(tabId);
}
function handleBeforeNavigate( details ) {
if (details.frameId == 0) {
cfInfo.delInfo( details.tabId );
updateStatus( details.tabId );
}
}
function handleTabUpdate( tabId, changeInfo, tabInfo ) {
if ("url" in changeInfo) {
updateStatus( tabId );
}
}
function handleTabClose( tabId, removeInfo ) {
cfInfo.delInfo(tabId);
}
function handleTabReplace( newId, oldId ) {
cfInfo.delInfo(oldId);
}
// triggered by popup script
function handleConnect(port) {
port.onMessage.addListener( function(tabId) {
var info = cfInfo.getInfo(tabId);
var msg;
if (info) {
msg = {
result: info.result,
counts: mapToObject(info.domainCounter.counts)
};
} else {
msg = null;
}
port.postMessage(msg);
});
}
chrome.webRequest.onHeadersReceived.addListener(
cfdetect,
{ urls: [ "<all_urls>" ] },
[ "responseHeaders" ]
);
chrome.webNavigation.onBeforeNavigate.addListener( handleBeforeNavigate );
chrome.tabs.onUpdated.addListener( handleTabUpdate );
chrome.tabs.onRemoved.addListener( handleTabClose );
chrome.tabs.onReplaced.addListener( handleTabReplace );
chrome.runtime.onConnect.addListener( handleConnect );
// vim: set expandtab ts=4 sw=4 :

5
icons/LICENSE Normal file
View file

@ -0,0 +1,5 @@
The drawing in "cf.svg" is derived from the "cloud" icon in
Font Awesome by Dave Gandy - http://fontawesome.io .
It is licensed under the SIL Open Font License 1.1,
http://scripts.sil.org/OFL .

BIN
icons/cf-green-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/cf-green-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/cf-green-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
icons/cf-grey-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

BIN
icons/cf-grey-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
icons/cf-grey-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
icons/cf-orange-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/cf-orange-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
icons/cf-orange-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
icons/cf-red-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
icons/cf-red-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
icons/cf-red-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

167
icons/cf.svg Normal file
View file

@ -0,0 +1,167 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 -256 1920 1920"
id="svg2989"
version="1.1"
inkscape:version="0.48.4 r9939"
width="100%"
height="100%"
sodipodi:docname="cloudflare-grey.svg">
<metadata
id="metadata2999">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<cc:license
rdf:resource="http://scripts.sil.org/OFL" />
</cc:Work>
<cc:License
rdf:about="http://scripts.sil.org/OFL">
<cc:permits
rdf:resource="http://scripts.sil.org/pub/OFL/Reproduction" />
<cc:permits
rdf:resource="http://scripts.sil.org/pub/OFL/Distribution" />
<cc:permits
rdf:resource="http://scripts.sil.org/pub/OFL/Embedding" />
<cc:permits
rdf:resource="http://scripts.sil.org/pub/OFL/DerivativeWorks" />
<cc:requires
rdf:resource="http://scripts.sil.org/pub/OFL/Notice" />
<cc:requires
rdf:resource="http://scripts.sil.org/pub/OFL/Attribution" />
<cc:requires
rdf:resource="http://scripts.sil.org/pub/OFL/ShareAlike" />
<cc:requires
rdf:resource="http://scripts.sil.org/pub/OFL/DerivativeRenaming" />
<cc:requires
rdf:resource="http://scripts.sil.org/pub/OFL/BundlingWhenSelling" />
</cc:License>
</rdf:RDF>
</metadata>
<defs
id="defs2997" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1551"
inkscape:window-height="878"
id="namedview2995"
showgrid="false"
inkscape:zoom="0.35833333"
inkscape:cx="960"
inkscape:cy="960"
inkscape:window-x="49"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg2989"
showguides="true"
inkscape:guide-bbox="true">
<sodipodi:guide
orientation="1,0"
position="691.56977,1638.2267"
id="guide3752" />
<sodipodi:guide
orientation="0,1"
position="475.45422,1047.9833"
id="guide3754" />
</sodipodi:namedview>
<path
style="fill:#000000;fill-opacity:1"
inkscape:connector-curvature="0"
id="path2993"
d="m 0,990.205 q 0,159 112.5,271.5 112.5,112.5 271.5,112.5 h 1088 q 185,0 316.5,-131.5 131.5,-131.5 131.5,-316.5 0,-132 -71,-241.5 -71,-109.5 -187,-163.5 2,-28 2,-43 0,-212 -150,-362 -150,-149.999997 -362,-149.999997 -158.00002,0 -286.5,88.000001 Q 737,142.205 678,284.205 q -70,-62 -166,-62 -106,0 -181,75 -75,75 -75,181 0,75 41,138 -129,30 -212.999998,134.5 Q 0,855.205 0,990.205 z" />
<g
id="g3901"
transform="translate(-23.203018,-30)">
<path
transform="translate(0,-256)"
inkscape:connector-curvature="0"
id="path3758"
d="m 691.56977,902.0167 0,-510.88298"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3758-3"
d="m 691.39826,1157.3339 0,-510.88294"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3758-5"
d="m 689.46328,646.15278 -510.88298,0"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path3758-5-3"
d="m 1203.847,646.31395 -510.88296,0"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
sodipodi:open="true"
sodipodi:end="3.1415927"
sodipodi:start="1.5707963"
transform="translate(-73.498961,-181.39926)"
d="m 877.21656,827.61626 c -61.83101,0 -111.95495,-50.12393 -111.95495,-111.95494 0,0 0,-1e-5 0,-1e-5"
sodipodi:ry="111.95494"
sodipodi:rx="111.95494"
sodipodi:cy="715.66132"
sodipodi:cx="877.21655"
id="path3845"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
sodipodi:open="true"
sodipodi:end="3.1415927"
sodipodi:start="1.5707963"
transform="matrix(-1,0,0,1,1456.8026,-181.52326)"
d="m 877.21656,827.61626 c -61.83101,0 -111.95495,-50.12393 -111.95495,-111.95494 0,0 0,-1e-5 0,-1e-5"
sodipodi:ry="111.95494"
sodipodi:rx="111.95494"
sodipodi:cy="715.66132"
sodipodi:cx="877.21655"
id="path3845-5"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
sodipodi:open="true"
sodipodi:end="3.1415927"
sodipodi:start="1.5707963"
transform="matrix(-1,0,0,-1,1456.4916,1473.8323)"
d="m 877.21656,827.61626 c -61.83101,0 -111.95495,-50.12393 -111.95495,-111.95494 0,0 0,-1e-5 0,-1e-5"
sodipodi:ry="111.95494"
sodipodi:rx="111.95494"
sodipodi:cy="715.66132"
sodipodi:cx="877.21655"
id="path3845-5-6"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
<path
sodipodi:open="true"
sodipodi:end="3.1415927"
sodipodi:start="1.5707963"
transform="matrix(0,1,-1,0,1518.9723,-118.89156)"
d="m 877.21656,827.61626 c -61.83101,0 -111.95495,-50.12393 -111.95495,-111.95494 0,0 0,-1e-5 0,-1e-5"
sodipodi:ry="111.95494"
sodipodi:rx="111.95494"
sodipodi:cy="715.66132"
sodipodi:cx="877.21655"
id="path3845-5-6-2"
style="fill:none;stroke:#ffffff;stroke-width:40;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
sodipodi:type="arc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

17
icons/make.sh Executable file
View file

@ -0,0 +1,17 @@
#!/bin/bash
colors="red:#cc0000 green:#00cc00 orange:#ddaa00 grey:#cccccc"
for res in 16 32 64; do
for color in ${colors}; do
colname=${color%:*}
colspec=${color#*:}
convert -background none cf.svg \
+level-colors "${colspec}," \
-colorspace RGB \
-resize "${res}" \
-colorspace sRGB \
"cf-${colname}-${res}.png"
done
done

31
manifest.json Normal file
View file

@ -0,0 +1,31 @@
{
"manifest_version": 2,
"name": "Detect Cloudflare",
"homepage_url": "https://git.massivebox.net/massivebox/cf-detect-chrome",
"description": "Adds an icon to the toolbar which indicates whether the current page uses Cloudflare. If it does, the icon changes color. Detection is performed by analyzing the response headers of all requests.",
"version": "0.7",
"icons": {
"16": "icons/cf-grey-16.png",
"32": "icons/cf-grey-32.png",
"64": "icons/cf-grey-64.png"
},
"permissions": [
"webRequest",
"webNavigation",
"tabs",
"<all_urls>"
],
"background": {
"scripts": [ "background.js" ]
},
"browser_action": {
"browser_style": true,
"default_title": "Indicates whether this page uses Cloudflare",
"default_icon": {
"16": "icons/cf-grey-16.png",
"32": "icons/cf-grey-32.png",
"64": "icons/cf-grey-64.png"
},
"default_popup": "popup.html"
}
}

16
popup.css Normal file
View file

@ -0,0 +1,16 @@
body {
padding: 0em 1em;
}
p#status {
font-weight: bold;
}
ul {
margin-top: -1ex;
padding-left: 1em;
}
.count {
color: #cc0000;
}

13
popup.html Normal file
View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div id="top">
<p id="status">Detection Status.</p>
</div>
<script src="popup.js"></script>
</body>
</html>

54
popup.js Normal file
View file

@ -0,0 +1,54 @@
const statusText = {
0: "No requests have been processed yet.",
1: "No requests were served by Cloudflare.",
2: "Requests for these domains were served by Cloudflare:",
3: "Requests for these domains were served by Cloudflare:",
99: "Detection result unavailable."
};
var getTab = chrome.tabs.query( { active:true, currentWindow:true }, function(tabs){
try {
var tab = tabs[0];
var port = chrome.runtime.connect();
port.postMessage(tab.id);
port.onMessage.addListener( function(msg) {
port.disconnect();
if (msg) {
writeStatus(msg.result);
populatePopup(msg.counts);
} else {
writeStatus(0);
}
});
}catch (error){
writeStatus(99);
console.log(`CF-Detect-Popup: ${error}`);
}
});
function writeStatus( st ) {
var p = document.getElementById("status");
p.textContent = statusText[st];
}
function populatePopup( domainCounts ) {
var ndomain = 0;
var div = document.getElementById("top");
var ul = document.createElement("ul");
for (var domain in domainCounts) {
if (!domainCounts.hasOwnProperty(domain)) continue;
++ndomain;
var count = domainCounts[domain];
var li = document.createElement("li");
var text = document.createTextNode(`${domain}: `);
var span = document.createElement("span");
span.setAttribute("class", "count");
span.textContent = `${count}`;
li.appendChild(text);
li.appendChild(span);
ul.appendChild(li);
}
if (ndomain>0) div.appendChild(ul);
}
// vim: set expandtab ts=4 sw=4 :