Skip to content

Commit

Permalink
0.6.0b
Browse files Browse the repository at this point in the history
- add options page
- allow changing the period to ignore HTTP-only sites
  • Loading branch information
claustromaniac committed Jan 10, 2019
1 parent 806b75e commit d7d8856
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 31 deletions.
62 changes: 62 additions & 0 deletions src/bg/classes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
class Settings {
constructor() {
this.defaults = {
'ignored': {}, // hostname:unixTimeStamp pairs
'ignorePeriod': 7 //-1 = permanent, 0 = session-only, 1+ = X days
};
this.loading = (async () => {
let saved = await browser.storage.local.get(this.defaults);
this.all = saved;
await browser.storage.local.set(saved);
browser.storage.onChanged.addListener((changes, area) => {
console.debug(`HTTPZ: ${area} storage changed`);
for (const i in changes) {
if (changes[i].hasOwnProperty('newValue')) this[i] = changes[i].newValue;
else if (changes[i].hasOwnProperty('oldValue')) delete this[i];
}
});
console.log('HTTPZ: settings loaded');
delete this.loading;
})();
}
get all() {
return (async () => {
if (this.loading) await this.loading;
const val = {};
for (const i in this.defaults) val[i] = this[i];
return val;
})();
}
set all(obj) {
for (const i in obj) this[i] = obj[i];
}
save() {
this.all.then(r => {
browser.storage.local.set(r);
});
}
}

class DelayableAction {
constructor(step, timeout, callback) {
this.callback = callback;
this.step = step;
this.timeout = timeout;
}
run() {
this.count = 0;
if (!this.timerID) {
this.countdown = this.timeout;
this.timerID = setInterval(t => {
if (++t.count >= t.step || !--t.countdown) t.stop();
}, 1000, this);
}
}
stop() {
if (this.timerID) {
clearInterval(this.timerID);
this.timerID = null;
this.callback();
}
}
}
1 change: 1 addition & 0 deletions src/bg/globals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const settings = new Settings();
4 changes: 4 additions & 0 deletions src/bg/runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
// triggered by options page script
return settings.all;
});
62 changes: 35 additions & 27 deletions src/bg/webRequest.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
'use strict';

const processed = new Set();
const ignored = new Set(['localhost', 'loopback']);
const stackCleaner = new DelayableAction(60, 120, () => {
processed.clear();
});
const saver = new DelayableAction(10, 60, () => {
settings.save();
});
const filter = {urls: ["https://*/*"], types: ['main_frame']};
const error_rx = /^SEC_ERROR|(?:_|\b)(?:SSL|TLS|CERT)(?:_|\b)|\b[Cc]ertificate/;
const loopback_rx = /^127\.\d+\.\d+\.\d+$/;
const other_errors = new Set([
'MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED',
'NS_ERROR_CONNECTION_REFUSED',
Expand All @@ -13,42 +17,46 @@ const other_errors = new Set([
'Peer using unsupported version of security protocol.'
]);

const step = 60;
let timeout;
let counter;
let timerID;

function stopTimer() {
if (!timerID) return;
processed.clear();
clearInterval(timerID);
timerID = false;
}
/** ---------- Functions ---------- **/

function runTimer() {
counter = 0;
if (!timerID) {
timeout = 120;
timerID = setInterval(() => {
if (++counter >= step || !--timeout) stopTimer();
}, 1000);
function ignore(host) {
if (!settings.ignored[host]) {
settings.ignored[host] = Date.now();
if (settings.ignorePeriod) saver.run();
}
}

function downgrade(url, d) {
ignored.add(url.hostname);
ignore(url.hostname);
url.protocol = 'http:';
browser.tabs.update(
d.tabId,
{ loadReplace: true, url: url.toString() }
);
}

function daysSince(unixTimeStamp) {
return (Date.now() - unixTimeStamp) / 86400000;
}

/** ------------------------------ **/

browser.webRequest.onBeforeRequest.addListener(d => {
const url = new URL(d.url);
if (!ignored.has(url.hostname) && !loopback_rx.test(url.hostname)) {
runTimer();
if (settings.ignorePeriod > 0) {
const ignoredTime = settings.ignored[url.hostname];
if (ignoredTime && daysSince(ignoredTime) > settings.ignorePeriod) {
delete settings.ignored[url.hostname];
}
}
if (
!settings.ignored[url.hostname] &&
url.hostname !== 'localhost' &&
url.hostname !== 'loopback' &&
!/^127\.\d+\.\d+\.\d+$/.test(url.hostname)
) {
processed.add(url.hostname);
stackCleaner.run();
url.protocol = 'https:';
return {redirectUrl: url.toString()}
}
Expand All @@ -58,22 +66,22 @@ browser.webRequest.onBeforeRedirect.addListener(d => {
const url = new URL(d.url);
const newTarget = new URL(d.redirectUrl);
if (url.hostname === newTarget.hostname) {
if (newTarget.protocol === 'http:') ignored.add(url.hostname);
if (newTarget.protocol === 'http:') ignore(url.hostname);
} else if (processed.has(url.hostname)) {
processed.add(newTarget.hostname);
runTimer();
stackCleaner.run();
}
}, filter);

browser.webRequest.onCompleted.addListener(d => {
const url = new URL(d.url);
if ( d.statusCode >= 400 && processed.has(url.hostname)
&& !ignored.has(url.hostname) ) downgrade(url, d);
&& !settings.ignored[url.hostname] ) downgrade(url, d);
}, filter);

browser.webRequest.onErrorOccurred.addListener(d => {
const url = new URL(d.url);
if ( processed.has(url.hostname) && !ignored.has(url.hostname) &&
if ( processed.has(url.hostname) && !settings.ignored[url.hostname] &&
( error_rx.test(d.error) || other_errors.has(d.error) )
) downgrade(url, d);
else console.info(`Error info: ${d.error}`);
Expand Down
18 changes: 14 additions & 4 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@
"applications": {
"gecko": {
"id": "httpz@cm.org",
"strict_min_version": "54.0"
"strict_min_version": "57.0"
}
},
"background": {
"scripts": [ "httpz.js" ]
"scripts": [
"bg/classes.js",
"bg/globals.js",
"bg/webRequest.js",
"bg/runtime.js"
]
},
"description": "Zapping those inZecure connections into sublime submission since ~2018.",
"author": "claustromaniac",
"homepage_url": "https://github.com/claustromaniac/httpz",
"icons": {"64": "httpz.svg"},
"icons": {"64": "assets/httpz.svg"},
"manifest_version": 2,
"name": "HTTPZ",
"options_ui": {
"browser_style": true,
"page": "pages/options.htm"
},
"permissions": [
"storage",
"webRequest",
"webRequestBlocking",
"<all_urls>"
],
"version": "0.5.4"
"version": "0.6.0b"
}
32 changes: 32 additions & 0 deletions src/pages/options.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
* { box-sizing: border-box; }
body { display: table; }
button { margin-left: 0.25em; }
#days {
background-color: #FFF;
color: #000;
text-align: center;
width: 4em;
}
.fb {
align-items: center;
display: flex;
flex-flow: row wrap;
}
.fb * { margin: auto inherit; }
.hidden {
opacity: 0;
transition: opacity 500ms;
}
.shown { opacity: 1; }
.status {
font-weight: 300;
margin-left: 1em;
}
.saved {
color: #021;
text-shadow: 0 0 0.2em #0F8;
}
.error {
color: #300;
text-shadow: 0 0 0.2em #F00;
}
23 changes: 23 additions & 0 deletions src/pages/options.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="options.css" rel="stylesheet">
</head>
<body>
<p>Check again if a site supports HTTPS...</p>
<div class="browser-style fb">
<input id="session" name="period" type="radio"><label for="session">after Firefox is restarted</label>
</div>
<div class="browser-style fb">
<input id="xdays" name="period" type="radio"><label for="xdays">after
<input id="days" name="days" type="number" min=1 required> days</label>
</div>
<div class="browser-style fb">
<input id="permanent" name="period" type="radio"><label for="permanent">never</label>
</div><br>
<button class="browser-style" id="clear">Forget sites</button>
<button class="browser-style" id="save">Save</button><span class="hidden" id="status"></span>
<script src="options.js"></script>
</body>
</html>
64 changes: 64 additions & 0 deletions src/pages/options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
'use strict';

var settings;

const ui = (() => {
const dom = document.getElementsByTagName('*');
return new Proxy(dom, {
get: function(obj, prop) {
return obj[prop];
},
set: function(obj, prop, val) {
obj[prop].value = val;
return true;
}
});
})();

function setStatus(msg, type) {
ui.status.textContent = msg;
ui.status.className = `status shown ${type}`;
setTimeout(() => {
ui.status.className = 'status hidden';
}, 2500);
}

browser.runtime.sendMessage(true).then(msg => {
settings = msg;
ui.session.checked = !msg.ignorePeriod;
ui.xdays.checked = msg.ignorePeriod > 0;
ui.days.disabled = !ui.xdays.checked;
ui.permanent.checked = msg.ignorePeriod === -1;
if (ui.xdays.checked) ui.days.value = msg.ignorePeriod;
const changePeriod = e => {
ui.days.disabled = !ui.xdays.checked;
if (ui.xdays.checked) {
ui.days.value = settings.ignorePeriod > 0 ? settings.ignorePeriod : 1;
}
};
ui.session.onchange = changePeriod;
ui.xdays.onchange = changePeriod;
ui.permanent.onchange = changePeriod;
ui.clear.onclick = e => {
browser.storage.local.set({ignored: {}}).then(() => {;
setStatus('List of sites cleared', '');
});
};
ui.save.onclick = e => {
if (ui.session.checked) {
settings.ignorePeriod = 0;
settings.ignored = {};
} else if (ui.xdays.checked) {
if (!/^\d+$/.test(ui.days.value.toString())) {
setStatus('Invalid input', 'error');
return;
}
settings.ignorePeriod = +ui.days.value;
} else settings.ignorePeriod = -1;
setStatus('. . .', '');
browser.storage.local.set(settings)
.then(() => {
setStatus('Saved!', 'saved');
});
};
});

0 comments on commit d7d8856

Please sign in to comment.