diff --git a/bin/goldilocks.js b/bin/goldilocks.js index c981394..90ec4ce 100755 --- a/bin/goldilocks.js +++ b/bin/goldilocks.js @@ -155,7 +155,7 @@ function readConfigAndRun(args) { if (ports.length === 0) { // I don't think we want to prevent the rest of the app from running in - // this case like we do for TCP, do don't call reject. + // this case like we do for TCP, so don't call reject. console.warn('could not bind to the desired ports for DNS'); Object.keys(failed).forEach(function (key) { console.log('[error bind]', key, failed[key].code); diff --git a/goldilocks.example.yml b/goldilocks.example.yml index f7722a4..80f1283 100644 --- a/goldilocks.example.yml +++ b/goldilocks.example.yml @@ -22,6 +22,12 @@ http: allow_insecure: false primary_domain: localhost.foo.daplie.me modules: + - name: redirect + domains: + - localhost.beta.daplie.me + status: 301 + from: /old/path/*/other/* + to: /path/new/:2/something/:1 - name: proxy domains: - localhost.daplie.me diff --git a/lib/domain-utils.js b/lib/domain-utils.js new file mode 100644 index 0000000..39c507f --- /dev/null +++ b/lib/domain-utils.js @@ -0,0 +1,30 @@ +'use strict'; + +module.exports.match = function (pattern, domainname) { + // Everything matches '*' + if (pattern === '*') { + return true; + } + + if (/^\*./.test(pattern)) { + // get rid of the leading "*." to more easily check the servername against it + pattern = pattern.slice(2); + return pattern === domainname.slice(-pattern.length); + } + + // pattern doesn't contains any wildcards, so exact match is required + return pattern === domainname; +}; + +module.exports.separatePort = function (fullHost) { + var match = /^(.*?)(:\d+)?$/.exec(fullHost); + + if (match[2]) { + match[2] = match[2].replace(':', ''); + } + + return { + host: match[1] + , port: match[2] + }; +}; diff --git a/lib/match-domain.js b/lib/match-domain.js deleted file mode 100644 index e4a8a1d..0000000 --- a/lib/match-domain.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -module.exports.match = function (pattern, servername) { - // Everything matches '*' - if (pattern === '*') { - return true; - } - - if (/^\*./.test(pattern)) { - // get rid of the leading "*." to more easily check the servername against it - pattern = pattern.slice(2); - return pattern === servername.slice(-pattern.length); - } - - // pattern doesn't contains any wildcards, so exact match is required - return pattern === servername; -}; diff --git a/lib/modules/http.js b/lib/modules/http.js index 72092b3..e4db50a 100644 --- a/lib/modules/http.js +++ b/lib/modules/http.js @@ -4,7 +4,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { var express = require('express'); var app = express(); var adminApp = require('./admin').create(deps, conf); - var domainMatches = require('../match-domain').match; + var domainMatches = require('../domain-utils').match; + var separatePort = require('../domain-utils').separatePort; var proxyRoutes = []; var adminDomains = [ @@ -14,8 +15,16 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { , /\balpha\.localhost\./ ]; + function moduleMatchesHost(req, mod) { + var host = separatePort(req.headers.host).host; + + return mod.domains.some(function (pattern) { + return domainMatches(pattern, host); + }); + } + function verifyHost(fullHost) { - var host = /^(.*?)(:\d+)?$/.exec(fullHost)[1]; + var host = separatePort(fullHost).host; if (host === 'localhost') { return fullHost.replace(host, 'localhost.daplie.me'); @@ -50,7 +59,7 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { return; } - var port = (/:(\d+)$/.exec(req.headers.host) || [])[1]; + var port = separatePort(req.headers.host).port; if (!redirecters[port]) { redirecters[port] = require('redirect-https')({ port: port @@ -115,24 +124,14 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { return { web: function (req, res, next) { - var hostname = req.headers.host.split(':')[0]; - var relevant = mod.domains.some(function (pattern) { - return domainMatches(pattern, hostname); - }); - - if (relevant) { + if (moduleMatchesHost(req, mod)) { proxy.web(req, res); } else { next(); } } , ws: function (req, socket, head, next) { - var hostname = req.headers.host.split(':')[0]; - var relevant = mod.domains.some(function (pattern) { - return domainMatches(pattern, hostname); - }); - - if (relevant) { + if (moduleMatchesHost(req, mod)) { proxy.ws(req, socket, head); } else { next(); @@ -141,6 +140,36 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { }; } + function createRedirectRoute(mod) { + // Escape any characters that (can) have special meaning in regular expression + // but that aren't the special characters we have interest in. + var from = mod.from.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&'); + // Then modify the characters we are interested in so they do what we want in + // the regular expression after being compiled. + from = from.replace(/\*/g, '(.*)'); + var fromRe = new RegExp('^' + from + '/?$'); + + return function (req, res, next) { + if (!moduleMatchesHost(req, mod)) { + next(); + return; + } + + var match = fromRe.exec(req.url); + if (!match) { + next(); + return; + } + + var to = mod.to; + match.slice(1).forEach(function (globMatch, index) { + to = to.replace(':'+(index+1), globMatch); + }); + res.writeHead(mod.status, { 'Location': to }); + res.end(); + }; + } + function createStaticRoute(mod) { var getStaticApp, staticApp; if (/:hostname/.test(mod.root)) { @@ -160,13 +189,8 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { } return function (req, res, next) { - var hostname = req.headers.host.split(':')[0]; - var relevant = mod.domains.some(function (pattern) { - return domainMatches(pattern, hostname); - }); - - if (relevant) { - getStaticApp(hostname)(req, res, next); + if (moduleMatchesHost(req, mod)) { + getStaticApp(separatePort(req.headers.host).host)(req, res, next); } else { next(); } @@ -183,6 +207,9 @@ module.exports.create = function (deps, conf, greenlockMiddleware) { proxyRoutes.push(proxyRoute); app.use(proxyRoute.web); } + else if (mod.name === 'redirect') { + app.use(createRedirectRoute(mod)); + } else if (mod.name === 'static') { app.use(createStaticRoute(mod)); } diff --git a/lib/modules/tls.js b/lib/modules/tls.js index 1b6b606..a292c93 100644 --- a/lib/modules/tls.js +++ b/lib/modules/tls.js @@ -5,7 +5,7 @@ module.exports.create = function (deps, config, netHandler) { var parseSni = require('sni'); var greenlock = require('greenlock'); var localhostCerts = require('localhost.daplie.me-certificates'); - var domainMatches = require('../match-domain').match; + var domainMatches = require('../domain-utils').match; function extractSocketProp(socket, propName) { // remoteAddress, remotePort... ugh... https://github.com/nodejs/node/issues/8854