walnut.js/lib/package-server.js

164 lines
4.4 KiB
JavaScript

'use strict';
var escapeStringRegexp = require('escape-string-regexp');
var runApi = require('./package-server-apis').runApi;
var layerItUp = require('./package-server-static').layerItUp;
function compileVhosts(vhostsMap) {
var results = {
patterns: []
, conflictsMap: {}
, matchesMap: {}
};
// compli
Object.keys(vhostsMap).forEach(function (key) {
var vhost = vhostsMap[key];
var bare;
var www;
if ('.' === vhost.hostname[0]) {
// for consistency
// TODO this should happen at the database level
vhost.hostname = '*' + vhost.hostname;
}
if ('*' === vhost.hostname[0]) {
// TODO check that we are not trying to redirect a tld (.com, .co.uk, .org, etc)
// tlds should follow the global policy
if (vhost.hostname[1] && '.' !== vhost.hostname[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 pattern '" + vhost.hostname + "'");
results.conflictsMap[vhost.hostname] = vhost;
}
// nix the '*' for easier matching
vhost.hostname = vhost.hostname.slice(1);
// except the default
if (!vhost.hostname) {
vhost.hostname = '*';
}
if (results.conflictsMap[vhost.hostname]) {
console.warn("[NON-FATAL ERROR]: duplicate entry for pattern '" + vhost.hostname + "'");
}
results.conflictsMap[vhost.hostname] = vhost;
results.patterns.push(vhost);
return;
}
bare = vhost.hostname.replace(/^www\./i, '');
www = vhost.hostname.replace(/^(www\.)?/i, 'www.');
results.matchesMap[bare] = vhost;
results.matchesMap[www] = vhost;
});
results.patterns.sort(function (a, b) {
return b.id.length - a.id.length;
});
return results;
}
function mapToApp(opts, req, res, next) {
// opts = { config, deps, services }
var vhost;
var router;
var pkgConf = opts.config;
if (!pkgConf.vhostConf) {
pkgConf.vhostConf = compileVhosts(pkgConf.vhostsMap);
}
//console.log('req.hostname');
//console.log(req.hostname);
//console.log(Object.keys(pkgConf.vhostConf.matchesMap));
// TODO www vs no-www?
vhost = pkgConf.vhostConf.matchesMap[req.hostname];
if (!vhost) {
pkgConf.vhostConf.patterns.some(function (pkg) {
// TODO this should be done in the compile phase
if ('*' === pkg.id[0] && '.' === pkg.id[1]) {
pkg.id = pkg.id.slice(1);
}
if (pkg.id === req.hostname.slice(req.hostname.length - pkg.id.length)) {
vhost = pkg;
return true;
}
});
}
if (!vhost) {
next();
return;
}
// TODO don't modify route here (or in subloaders), modify some other variable instead
// TODO precompile RegExps and pre-sort app vs api
vhost.pathnames.some(function (routes) {
var pathname = routes.pathname;
if ('/' === pathname) {
pathname = '';
}
if (!routes._re_app) {
routes._re_app = new RegExp(escapeStringRegexp(pathname) + '(#|\\/|\\?|$)');
//console.log('[static re]', routes._re_app);
}
if (!routes._re_api) {
// TODO allow for special apis that do not follow convention (.well_known, webfinger, oauth3.html, etc)
routes._re_api = new RegExp(escapeStringRegexp(pathname + '/api/') + '([\\w\\.\\-]+)(\\/|\\?|$)');
//console.log('[api re]', routes._re_api);
}
if (routes._re_app.test(req.url)) {
router = routes;
return true;
}
// no need to test for api yet as it is a postfix
});
if (!router) {
//console.log('[no router for]', req.url);
next();
return;
}
// TODO .well-known can be an API (webfinger, letsencrypt, oauth3)
// or static (...???)
if (!router._re_api.test(req.url)) {
//console.log('[static router]');
//console.log(router._re_api, req.url);
layerItUp(pkgConf, router, req, res, function (err) {
if (err) {
next(err);
return;
}
if (/\/\.well-known([\/?]|$)/.test(req.url)) {
console.log('[TODO] handle .well-known as API');
// rewrite api as /api/org.ietf/.well-known ?
// pass through simply as /.well-known ?
// runApi(opts, router, req, res, next)
}
next();
});
return;
}
//console.log('[api router]', req.url);
return runApi(opts, router, req, res, next);
}
module.exports.compileVhosts = compileVhosts;
module.exports.mapToApp = mapToApp;