UNPKG

canonical-host

Version:

Redirect users to the canonical hostname for your site.

99 lines (84 loc) 3.16 kB
module.exports = canon var url = require('url') function canon (hosts) { if (!hosts) { throw new Error('Must provide at least one canonical hostname') } // default to not-https if (!hosts.match(/^https?:\/\//)) hosts = 'http://' + hosts hosts = [url.parse(hosts)] var statusCode = 301 for (var i = 1; i < arguments.length; i ++) { var a = arguments[i] if (typeof a === 'number') statusCode = a else { // default to not-https if (!a.match(/^https?:\/\//)) a = 'http://' + a hosts.push(url.parse(a)) } } var len = hosts.length return redirector(statusCode, hosts, hosts.length) } function redirector (statusCode, hosts, len) { return function cannon (req, res, cb) { var h = req.headers.host // not sending a hostname is not at ALL canonical! if (!h) { res.statusCode = 400 res.end('Bad request\n') return true } var ssl = !!req.socket.encrypted h = url.parse('http' + (ssl ? 's' : '') + '://' + h) var redir = hosts[0] FOR: for (var i = 0; i < len; i++) { var spec = hosts[i] // if it's a host we like, and https is not required, // or it is required and provided, then that's a match. var hostNameMatch = spec.hostname === h.hostname // if the port is not specified, or not sent, then pretend // that it matches. var portMatch = spec.port === h.port || !spec.port || !h.port // the protocol wasn't set to https, then accept anything. // if we must have https, then redirect to that. // Handy for servers that listen on both :80 and :443, and want // to forward all traffic to the :443 server, but with one // canon function. var protoMatch = (spec.protocol === h.protocol) || (spec.protocol !== 'https:') // using a bitmask for this is silly, but it's the easiest way // to make sure I cover every possible case. var mask = (hostNameMatch ? 4 : 0) + (portMatch ? 2 : 0) + (protoMatch ? 1 : 0) switch (mask) { case 7: // user has sent canonical hostname! horray! if (typeof cb === 'function') cb() return false case 6: // hostName and host, but not protocol case 5: // hostname and proto, but not port, and it was specified case 4: // similar to 6. // This is a close enough match. Redirect here, // unless another canonical match already called dibs. if (!redir) redir = spec continue case 3: // port and proto, but not hostname case 2: // only port, but not hostname/ssl case 1: // only protocol matches. Keep looking. case 0: // nothing matches. keep looking. continue } throw new Error('unreachable') } redir = redir.protocol + '//' + redir.host + url.parse(req.url).path redir = url.format(redir) res.statusCode = statusCode res.setHeader('location', redir) res.end('<html>Moved: <a href="' + redir + '">' + redir + '</a>\n') return true } }