add hostname matching and https exception

This commit is contained in:
AJ ONeal 2015-11-21 06:42:23 +00:00
parent 61bb3e63e7
commit 59dd087084
4 changed files with 251 additions and 4 deletions

123
lib/hostname-redirects.js Normal file
View File

@ -0,0 +1,123 @@
'use strict';
// TODO detect infinite redirects
module.exports.compile = module.exports.sortOpts = function (opts) {
var redirects = opts.redirects;
var dups = {};
var results = {
conflicts: {}
, patterns: []
, matchesMap: {}
};
redirects.forEach(function (r) {
var bare;
var www;
if ('.' === r.id[0]) {
// for consistency
// TODO this should happen at the database level
r.id = '*' + r.id;
}
if ('*' === r.id[0]) {
// TODO check that we are not trying to redirect a tld (.com, .co.uk, .org, etc)
// tlds should follow the global policy
if (r.id[1] && '.' !== r.id[1]) {
// this is not a good place to throw as the consequences of a bug would be
// very bad, but errors should never be silent, so we'll compromise
console.warn("[NON-FATAL ERROR]: ignoring redirect pattern '" + r.id + "'");
results.conflicts[r.id] = r;
}
// nix the '*' for easier matching
r.id = r.id.slice(1);
if (!r.id) {
r.id = '*';
}
if (dups[r.id]) {
results.conflicts[r.id] = r;
console.warn("[NON-FATAL ERROR]: duplicate entry for redirect pattern '" + r.id + "'");
}
dups[r.id] = true;
results.patterns.push(r);
return;
}
bare = r.id.replace(/^www\./i, '');
www = r.id.replace(/^(www\.)?/i, 'www.');
if (true === r.value) {
// implicit add www
results.matchesMap[bare] = www;
results.matchesMap[www] = www;
} else if (false === r.value) {
// implicit remove www
results.matchesMap[bare] = bare;
results.matchesMap[www] = bare;
} else if (!r.value) {
// (null, '', 0, undefined)
// explicitly no change
results.matchesMap[r.id] = r.id;
} else {
// explicit value
results.matchesMap[r.id] = r.value;
}
});
results.patterns.sort(function (a, b) {
return b.id.length - a.id.length;
});
return results;
};
module.exports.redirectTo = function (hostname, opts) {
var redir = opts.matchesMap[hostname];
if (redir) {
if (redir === hostname) {
return false;
}
return redir;
}
// longest to shortest
var hasWww = ('www.' === hostname.slice(0, 4));
//var noWww = (hasWww && hostname.slice(4)) || hostname;
//var yesWww = (hasWww && hostname) || ('www.' + hostname);
redir = false;
opts.patterns.some(function (r) {
// r.id begins with a dot, such as '.foo.example.com'
if (r.id !== hostname.slice(hostname.length - r.id.length)) {
// except for the default, which is an *
if ('*' !== r.id) {
return false;
}
}
if (true === r.value) {
// implicit add www
redir = hasWww ? hostname : ('www.' + hostname);
} else if (false === r.value) {
// implicit remove www
redir = hasWww ? hostname.slice(4) : hostname;
} else if (!r.value) {
// (null, '', 0, undefined)
// explicitly no change
redir = false;
} else {
// explicit value
redir = r.value;
}
return true;
});
if (redir === hostname) {
return false;
}
return redir;
};

View File

@ -1,14 +1,26 @@
module.exports.scrubTheDub = function (req, res) { 'use strict';
module.exports.scrubTheDub = function (req, res, redirectives) {
// hack for bricked app-cache // hack for bricked app-cache
// Also 301 redirects will not work for appcache (must issue html) // Also 301 redirects will not work for appcache (must issue html)
if (require('./unbrick-appcache').unbrick(req, res)) { if (require('./unbrick-appcache').unbrick(req, res)) {
return; return true;
} }
// TODO port number for non-443 // TODO port number for non-443
var escapeHtml = require('escape-html'); var escapeHtml = require('escape-html');
var newLocation = 'https://' + req.hostname.replace(/^www\./, '') + req.url; var newLocation;
var safeLocation = escapeHtml(newLocation); var safeLocation;
if (redirectives) {
newLocation = require('./hostname-redirects').redirectTo(req.hostname, redirectives);
if (!newLocation) {
return false;
}
} else {
newLocation = 'https://' + req.hostname.replace(/^www\./, '') + req.url;
}
safeLocation = escapeHtml(newLocation);
var metaRedirect = '' var metaRedirect = ''
+ '<html>\n' + '<html>\n'
@ -24,4 +36,6 @@ module.exports.scrubTheDub = function (req, res) {
; ;
res.end(metaRedirect); res.end(metaRedirect);
return true;
}; };

38
lib/pathname-redirects.js Normal file
View File

@ -0,0 +1,38 @@
/*
//var escapeRe;
//var insecureRedirects;
if (require('./unbrick-appcache').unbrick(req, res)) {
return;
}
// because I have domains for which I don't want to pay for SSL certs
insecureRedirects = (redirects||[]).sort(function (a, b) {
var hlen = b.from.hostname.length - a.from.hostname.length;
var plen;
if (!hlen) {
plen = b.from.path.length - a.from.path.length;
return plen;
}
return hlen;
}).forEach(function (redirect) {
var origHost = host;
if (!escapeRe) {
escapeRe = require('escape-string-regexp');
}
// TODO if '*' === hostname[0], omit '^'
host = host.replace(
new RegExp('^' + escapeRe(redirect.from.hostname))
, redirect.to.hostname
);
if (host === origHost) {
return;
}
url = url.replace(
new RegExp('^' + escapeRe(redirect.from.path))
, redirect.to.path
);
});
*/

View File

@ -0,0 +1,72 @@
'use strict';
var opts = {
redirects: [
{ "id": "*", "value": true }
, { "id": "ns2.redirect-www.org", "value": false }
, { "id": "hellabit.com", "value": false }
, { "id": "*.hellabit.com", "value": false }
, { "id": "redirect-www.org", "value": null }
, { "id": "www.redirect-www.org", "value": null }
, { "id": "no.redirect-www.org", "value": false }
, { "id": "*.redirect-www.org", "value": false }
, { "id": "*.yes.redirect-www.org", "value": true }
, { "id": "yes.redirect-www.org", "value": true }
, { "id": "*.maybe.redirect-www.org", "value": null }
, { "id": "maybe.redirect-www.org", "value": null }
, { "id": "blog.coolaj86.com", "value": 'coolaj86.com' } // TODO pathname
]
, matchesMap: null
, patternsMap: null
, patterns: null
};
var redirectTo = require('../lib/hostname-redirects').redirectTo;
var sortOpts = require('../lib/hostname-redirects').sortOpts;
var domains = {
// maybewww
'redirect-www.org': false
, 'www.redirect-www.org': false
, 'maybe.redirect-www.org': false
, 'www.maybe.redirect-www.org': false
// yeswww
, 'yes.redirect-www.org': 'www.yes.redirect-www.org'
, 'foo.yes.redirect-www.org': 'www.foo.yes.redirect-www.org'
// nowww
, 'www.no.redirect-www.org': 'no.redirect-www.org'
, 'www.foo.no.redirect-www.org': 'foo.no.redirect-www.org'
, 'ns2.redirect-www.org': false
, 'www.ns2.redirect-www.org': 'ns2.redirect-www.org'
, 'ns1.redirect-www.org': false
, 'www.ns1.redirect-www.org': 'ns1.redirect-www.org'
, 'hellabit.com': false
, 'www.hellabit.com': 'hellabit.com'
// default policy (yeswww)
, 'ahellabit.com': 'www.ahellabit.com'
, 'www.ahellabit.com': false
, 'example.com': 'www.example.com'
, 'www.example.com': false
};
var redirects = sortOpts(opts);
console.log(redirects);
Object.keys(domains).forEach(function (domain, i) {
var redir = domains[domain];
var result = redirectTo(domain, redirects);
if (redir !== result) {
throw new Error("For domain #" + i + " '" + domain + "' expected '" + redir + "' but got '" + result + "'");
}
});
console.log("Didn't throw any errors. Must have worked, eh?");
console.log("TODO: detect and report infinite redirects");