You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1061 lines
43 KiB

/* jshint -W097 */
/* jshint strict: false */
/* jslint node: true */
'use strict';
const express = require('express');
const fs = require('fs');
const path = require('path');
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
const LE = require(utils.controllerDir + '/lib/letsencrypt.js');
const mime = require('mime-types');
let session;// = require('express-session');
let cookieParser;// = require('cookie-parser');
let bodyParser;// = require('body-parser');
let AdapterStore;// = require(__dirname + '/../../lib/session.js')(session);
let passportSocketIo;// = require(__dirname + "/lib/passport.socketio.js");
let password;// = require(__dirname + '/../../lib/password.js');
let passport;// = require('passport');
let LocalStrategy;// = require('passport-local').Strategy;
let flash;// = require('connect-flash'); // TODO report error to user
let webServer = null;
let store = null;
let secret = 'Zgfr56gFe87jJOM'; // Will be generated by first start
let socketUrl = '';
let cache = {}; // cached web files
let ownSocket = false;
let lang = 'en';
let extensions = {};
let bruteForce = {};
let socketIoFile = null;
let webPreSettings = {};
let webByVersion = {};
let loginPage = null;
let adapter = new utils.Adapter({
name: 'web',
objectChange: (id, obj) => {
if (obj && obj.common && obj.common.webExtension && obj.native &&
(extensions[id.substring('system.adapter.'.length)] ||
obj.native.webInstance === '*' ||
obj.native.webInstance === 'adapter.namespace'
)
) {
adapter.setForeignState('system.adapter.' + adapter.namespace + '.alive', false, true, () => {
process.exit(-100);
});
return;
}
if (obj && obj.common && obj.common.webPreSettings) {
updatePreSettings(obj);
}
// 'system.adapter.'length = 15
const _id = id.substring(15).replace(/\.\d+$/, '');
if (obj && obj.common && obj.common.webByVersion) {
webByVersion[_id] = obj.common.version;
} else if (webByVersion[_id]) {
delete webByVersion[_id];
}
if (!ownSocket && id === adapter.config.socketio) {
if (obj && obj.common && obj.common.enabled && obj.native) {
socketUrl = ':' + obj.native.port;
} else {
socketUrl = '';
}
}
if (webServer.io) {
webServer.io.publishAll('objectChange', id, obj);
}
if (webServer.api && adapter.config.auth) {
webServer.api.objectChange(id, obj);
}
if (id === 'system.config') {
lang = obj && obj.common && obj.common.language ? obj.common.language : 'en';
}
// inform extensions
for (let e = 0; e < extensions.length; e++) {
try {
if (extensions[e].obj && extensions[e].obj.objectChange) {
extensions[e].obj.objectChange(id, obj);
}
} catch (err) {
adapter.log.error('Cannot call objectChange for "' + e + '": ' + err);
}
}
},
stateChange: (id, state) => {
if (webServer.io) webServer.io.publishAll('stateChange', id, state);
},
unload: callback => {
try {
adapter.log.info('terminating http' + (webServer.settings.secure ? 's' : '') + ' server on port ' + webServer.settings.port);
webServer.server.close();
adapter.log.info('terminated http' + (webServer.settings.secure ? 's' : '') + ' server on port ' + webServer.settings.port);
callback();
} catch (e) {
callback();
}
},
ready: () => {
// Generate secret for session manager
adapter.getForeignObject('system.config', (err, obj) => {
if (!err && obj) {
if (!obj.native || !obj.native.secret) {
obj.native = obj.native || {};
require('crypto').randomBytes(24, (ex, buf) => {
secret = buf.toString('hex');
adapter.extendForeignObject('system.config', {native: {secret: secret}});
main();
});
} else {
secret = obj.native.secret;
main();
}
} else {
adapter.logger.error('Cannot find object system.config');
}
});
// information about connected socket.io adapter
if (adapter.config.socketio && adapter.config.socketio.match(/^system\.adapter\./)) {
adapter.getForeignObject(adapter.config.socketio, (err, obj) => {
if ( obj && obj.common && obj.common.enabled && obj.native) {
socketUrl = ':' + obj.native.port;
}
});
// Listen for changes
adapter.subscribeForeignObjects(adapter.config.socketio);
} else {
socketUrl = adapter.config.socketio;
ownSocket = (socketUrl !== 'none');
}
// Read language
adapter.getForeignObject('system.config', (err, data) => {
if (data && data.common) lang = data.common.language || 'en';
});
}
});
function extractPreSetting(obj, attr) {
const parts = attr.split('.');
if (parts.length === 1) {
if ((obj && typeof obj === 'object') || (obj !== null && obj !== undefined)) {
return obj[attr];
} else {
return null;
}
} else {
attr = parts.shift();
if (obj[attr] && typeof obj[attr] === 'object') {
return extractPreSetting(obj[attr], parts.join('.'));
} else {
return null;
}
}
}
function updatePreSettings(obj) {
if (!obj || !obj.common) return;
if (obj.common.webPreSettings) {
for (let attr in obj.common.webPreSettings) {
if (!obj.common.webPreSettings.hasOwnProperty(attr)) continue;
webPreSettings[obj._id] = webPreSettings[obj._id] || {};
const _attr = attr.replace(/[^\w0-9]/g, '_');
webPreSettings[obj._id][_attr] = extractPreSetting(obj, obj.common.webPreSettings[attr]);
if (typeof webPreSettings[obj._id][_attr] === 'object') {
webPreSettings[obj._id][_attr] = JSON.stringify(webPreSettings[obj._id][_attr]);
} else {
webPreSettings[obj._id][_attr] = webPreSettings[obj._id][_attr].replace(/"/g, '\\"');
}
}
} else if (webPreSettings[obj._id]) {
delete webPreSettings[obj._id];
}
}
function getExtensionsAndSettings(callback) {
adapter.objects.getObjectView('system', 'instance', null, (err, doc) => {
if (err) {
if (callback) callback (err, []);
} else {
if (doc.rows.length === 0) {
if (callback) callback (null, []);
} else {
let res = [];
for (let i = 0; i < doc.rows.length; i++) {
const instance = doc.rows[i].value;
if (instance && instance.common) {
if (instance.common.enabled &&
instance.common.webExtension &&
(instance.native.webInstance === adapter.namespace || instance.native.webInstance === '*')) {
res.push(doc.rows[i].value);
}
if (instance.common.webPreSettings) {
updatePreSettings(instance);
}
if (instance.common.webByVersion) {
// 'system.adapter.'length = 15
const _id = doc.rows[i].value._id.substring(15).replace(/\.\d+$/, '');
webByVersion[_id] = instance.common.version;
}
}
}
if (callback) callback (null, res);
}
}
});
}
function main() {
getExtensionsAndSettings((err, ext) => {
if (err) adapter.log.error('Cannot read extensions: ' + err);
if (ext) {
for (let e = 0; e < ext.length; e++) {
if (ext[e] && ext[e].common) {
const instance = ext[e]._id.substring('system.adapter.'.length);
const name = instance.split('.')[0];
extensions[instance] = {
path: name + '/' + ext[e].common.webExtension,
config: ext[e]
};
}
}
}
if (adapter.config.secure) {
// Load certificates
adapter.getCertificates((err, certificates, leConfig) => {
adapter.config.certificates = certificates;
adapter.config.leConfig = leConfig;
webServer = initWebServer(adapter.config);
});
} else {
webServer = initWebServer(adapter.config);
}
// monitor extensions and pro keys
adapter.subscribeForeignObjects('system.adapter.*');
});
}
function readDirs(dirs, cb, result) {
result = result || [];
if (!dirs || !dirs.length) {
return cb && cb(result);
}
const dir = dirs.shift();
adapter.readDir(dir, '', (err, files) => {
if (!err && files && files.length) {
for (let f = 0; f < files.length; f++) {
if (files[f].file.match(/\.html$/)) {
result.push(dir + '/' + files[f].file);
}
}
}
setImmediate(readDirs, dirs, cb, result);
});
}
let indexHtml;
function getLinkVar(_var, obj, attr, link) {
if (attr === 'protocol') attr = 'secure';
if (_var === 'ip') {
link = link.replace('%' + _var + '%', '$host$');
} else
if (_var === 'instance') {
const instance = obj._id.split('.').pop();
link = link.replace('%' + _var + '%', instance);
} else {
if (obj) {
if (attr.match(/^native_/)) attr = attr.substring(7);
let val = obj.native[attr];
if (_var === 'bind' && (!val || val === '0.0.0.0')) val = '$host$';
if (attr === 'secure') {
link = link.replace('%' + _var + '%', val ? 'https' : 'http');
} else {
if (link.indexOf('%' + _var + '%') === -1) {
link = link.replace('%native_' + _var + '%', val);
} else {
link = link.replace('%' + _var + '%', val);
}
}
} else {
if (attr === 'secure') {
link = link.replace('%' + _var + '%', 'http');
} else {
if (link.indexOf('%' + _var + '%') === -1) {
link = link.replace('%native_' + _var + '%', '');
} else {
link = link.replace('%' + _var + '%', '');
}
}
}
}
return link;
}
function resolveLink(link, instanceObj, instancesMap) {
const vars = link.match(/%(\w+)%/g);
let _var;
let v;
let parts;
let result;
if (vars) {
// first replace simple patterns
for (v = vars.length - 1; v >= 0; v--) {
_var = vars[v];
_var = _var.replace(/%/g, '');
parts = _var.split('_');
// like "port"
if (_var.match(/^native_/)) {
link = getLinkVar(_var, instanceObj, _var, link);
vars.splice(v, 1);
} else
if (parts.length === 1) {
link = getLinkVar(_var, instanceObj, parts[0], link);
vars.splice(v, 1);
} else
// like "web.0_port"
if (parts[0].match(/\.[0-9]+$/)) {
link = getLinkVar(_var, instancesMap['system.adapter.' + parts[0]], parts[1], link);
vars.splice(v, 1);
}
}
let links = {};
let instances;
const adptr = parts[0];
// process web_port
for (v = 0; v < vars.length; v++) {
_var = vars[v];
_var = _var.replace(/%/g, '');
if (_var.match(/^native_/)) _var = _var.substring(7);
parts = _var.split('_');
if (!instances) {
instances = [];
// TODO !
for (let inst = 0; inst < 10; inst++) {
if (that.main.objects['system.adapter.' + adptr + '.' + inst]) {
instances.push(inst);
}
}
}
for (let i = 0; i < instances.length; i++) {
links[adptr + '.' + i] = {
instance: adptr + '.' + i,
link: getLinkVar(_var, instancesMap['system.adapter.' + adptr + '.' + i], parts[1], links[adptr + '.' + i] ? links[adptr + '.' + i].link : link)
};
}
}
if (instances) {
result = [];
let count = 0;
let firtsLink = '';
for (let d in links) {
if (links.hasOwnProperty(d)) {
result[links[d].instance] = links[d].link;
if (!firtsLink) firtsLink = links[d].link;
count++;
}
}
if (count < 2) {
link = firtsLink;
result = null;
}
}
}
return result || link;
}
function replaceInLink(link, instanceObj, instances) {
if (typeof link === 'object') {
const links = JSON.parse(JSON.stringify(link));
let first;
for (let v in links) {
if (links.hasOwnProperty(v)) {
links[v] = resolveLink(links[v], instanceObj, instances);
if (!first) first = links[v];
}
}
links.__first = first;
return links;
} else {
return resolveLink(link, instanceObj, instances);
}
}
function getListOfAllAdapters(callback) {
try {
// read all instances
adapter.objects.getObjectView('system', 'instance', {}, (err, instances) => {
adapter.objects.getObjectView('system', 'adapter', {}, (err, adapters) => {
let list = [];
let a;
let mapInstance = {};
for (let r = 0; r < instances.rows.length; r++) {
mapInstance[instances.rows[r].id] = instances.rows[r].value;
}
for (a = 0; a < adapters.rows.length; a++) {
const obj = adapters.rows[a].value;
let found = '';
if (instances && instances.rows) {
found = '';
// find if any instance of this adapter is exists and started
for (let i = 0; i < instances.rows.length; i++) {
let id = instances.rows[i].id;
let ids = id.split('.');
ids.pop();
id = ids.join('.');
if (id === obj._id && instances.rows[i].value.common && (instances.rows[i].value.common.enabled || instances.rows[i].value.common.onlyWWW)) {
found = instances.rows[i].id;
break;
}
}
}
if (found) {
if (obj.common.welcomeScreen || obj.common.welcomeScreenPro) {
if (obj.common.welcomeScreen) {
if (obj.common.welcomeScreen instanceof Array) {
for (let w = 0; w < obj.common.welcomeScreen.length; w++) {
// temporary disabled
if (obj.common.welcomeScreen[w].name === 'vis editor') {
continue;
}
if (obj.common.welcomeScreen[w].localLink && typeof obj.common.welcomeScreen[w].localLink === 'boolean') {
obj.common.welcomeScreen[w].localLink = obj.common.localLink;
}
if (obj.common.welcomeScreen[w].localLink) {
obj.common.welcomeScreen[w].id = found;
}
list.push(obj.common.welcomeScreen[w]);
}
} else {
if (obj.common.welcomeScreen.localLink && typeof obj.common.welcomeScreen.localLink === 'boolean') {
obj.common.welcomeScreen.localLink = obj.common.localLink;
}
if (obj.common.welcomeScreen.localLink) {
obj.common.welcomeScreen.id = found;
}
list.push(obj.common.welcomeScreen);
}
}
if (obj.common.welcomeScreenPro) {
if (obj.common.welcomeScreenPro instanceof Array) {
for (let ww = 0; ww < obj.common.welcomeScreenPro.length; ww++) {
let tile = Object.assign({}, obj.common.welcomeScreenPro[ww]);
tile.pro = true;
if (tile.localLink && typeof tile.localLink === 'boolean') {
tile.localLink = obj.common.localLink;
}
if (tile.localLink) {
tile.id = found;
}
list.push(tile);
}
} else {
let tile_ = Object.assign({}, obj.common.welcomeScreenPro);
tile_.pro = true;
if (tile_.localLink && typeof tile_.localLink === 'boolean') {
tile_.localLink = obj.common.localLink;
}
if (tile_.localLink) {
tile_.id = found;
}
list.push(tile_);
}
}
}
}
}
indexHtml = /*indexHtml || */fs.readFileSync(__dirname + '/www/index.html').toString();
list.sort((a, b) => {
if (a.order === undefined && b.order === undefined) {
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
return 0;
} else if (a.order === undefined) {
return -1;
} else if (b.order === undefined) {
return 1;
} else {
if (a.order > b.order) return 1;
if (a.order < b.order) return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
return 0;
}
});
// calculate localLinks
for (let t = 0; t < list.length; t++) {
if (list[t].localLink) {
list[t].localLink = resolveLink(list[t].localLink, mapInstance[list[t].id], mapInstance);
}
}
let text = 'systemLang = "' + lang + '";\n';
text += 'list = ' + JSON.stringify(list, null, 2) + ';\n';
// if login
text += 'let authEnabled = ' + adapter.config.auth + ';\n';
callback(null, indexHtml.replace('// -- PLACE THE LIST HERE --', text));
});
});
} catch (e) {
callback(e);
}
}
function getInfoJs(settings) {
let result = [
'var socketUrl = "' + socketUrl + '";',
'var socketSession = "' + '' + '";',
'window.sysLang = "' + lang + '";',
'window.socketForceWebSockets = ' + (settings.forceWebSockets ? 'true' : 'false') + ';'
];
for (const id in webPreSettings) {
if (webPreSettings.hasOwnProperty(id) && webPreSettings[id]) {
for (const attr in webPreSettings[id]) {
if (webPreSettings[id].hasOwnProperty(attr)) {
result.push(`window.${attr} = "${webPreSettings[id][attr]}";`);
}
}
}
}
return result.join(' ');
}
function prepareLoginTemplate() {
let def =
' font: 13px/20px \'Lucida Grande\', Tahoma, Verdana, sans-serif;\n' +
' color: #404040;\n' +
' background-color: #0ae;\n' +
' background-image: -webkit-gradient(linear, 0 0, 0 100%, color-stop(.5, rgba(255, 255, 255, .2)), color-stop(.5, transparent), to(transparent));\n' +
' background-image: -webkit-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' +
' background-image: -moz-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' +
' background-image: -ms-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' +
' background-image: -o-linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' +
' background-image: linear-gradient(rgba(255, 255, 255, .2) 50%, transparent 50%, transparent);\n' +
' background-size: 50px 50px;\n'
;
let template = fs.readFileSync(__dirname + '/www/login/index.html').toString('utf8');
if (adapter.config.loginBackgroundColor) {
def = 'background-color: ' + adapter.config.loginBackgroundColor + ';\n'
}
if (adapter.config.loginBackgroundImage) {
def += ' background-image: url(../' + adapter.namespace + '/login-bg.png);\n';
}
return template.replace('background: black;', def);
}
function initAuth(server, settings) {
session = require('express-session');
cookieParser = require('cookie-parser');
bodyParser = require('body-parser');
AdapterStore = require(utils.controllerDir + '/lib/session.js')(session, settings.ttl);
passportSocketIo = require('passport.socketio');
password = require(utils.controllerDir + '/lib/password.js');
passport = require('passport');
LocalStrategy = require('passport-local').Strategy;
flash = require('connect-flash'); // TODO report error to user
store = new AdapterStore({adapter: adapter});
passport.use(new LocalStrategy(
function (username, password, done) {
if (bruteForce[username] && bruteForce[username].errors > 4) {
let minutes = (new Date().getTime() - bruteForce[username].time);
if (bruteForce[username].errors < 7) {
if ((new Date().getTime() - bruteForce[username].time) < 60000) {
minutes = 1;
} else {
minutes = 0;
}
} else
if (bruteForce[username].errors < 10) {
if ((new Date().getTime() - bruteForce[username].time) < 180000) {
minutes = Math.ceil((180000 - minutes) / 60000);
} else {
minutes = 0;
}
} else
if (bruteForce[username].errors < 15) {
if ((new Date().getTime() - bruteForce[username].time) < 600000) {
minutes = Math.ceil((600000 - minutes) / 60000);
} else {
minutes = 0;
}
} else
if ((new Date().getTime() - bruteForce[username].time) < 3600000) {
minutes = Math.ceil((3600000 - minutes) / 60000);
} else {
minutes = 0;
}
if (minutes) {
return done('Too many errors. Try again in ' + minutes + ' ' + (minutes === 1 ? 'minute' : 'minutes') + '.', false);
}
}
adapter.checkPassword(username, password, (res) => {
if (!res) {
bruteForce[username] = bruteForce[username] || {errors: 0};
bruteForce[username].time = new Date().getTime();
bruteForce[username].errors++;
} else if (bruteForce[username]) {
delete bruteForce[username];
}
if (res) {
return done(null, username);
} else {
return done(null, false);
}
});
}
));
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
server.app.use(cookieParser());
server.app.use(bodyParser.urlencoded({
extended: true
}));
server.app.use(bodyParser.json());
server.app.use(bodyParser.text());
server.app.use(session({
secret: secret,
saveUninitialized: true,
resave: true,
cookie: {maxAge: settings.ttl * 1000},
store: store
}));
server.app.use(passport.initialize());
server.app.use(passport.session());
server.app.use(flash());
}
//settings: {
// "port": 8080,
// "auth": false,
// "secure": false,
// "bind": "0.0.0.0", // "::"
// "cache": false
//}
function initWebServer(settings) {
let server = {
app: null,
server: null,
io: null,
settings: settings
};
adapter.subscribeForeignObjects('system.config');
settings.ttl = parseInt(settings.ttl, 10) || 3600;
if (!settings.whiteListEnabled && settings.whiteListSettings) delete settings.whiteListSettings;
settings.defaultUser = settings.defaultUser || 'system.user.admin';
if (!settings.defaultUser.match(/^system\.user\./)) settings.defaultUser = 'system.user.' + settings.defaultUser;
if (settings.port) {
if (settings.secure) {
if (!settings.certificates) {
return null;
}
}
server.app = express();
server.app.disable('x-powered-by');
if (settings.auth) {
initAuth(server, settings);
let autoLogonOrRedirectToLogin = (req, res, next, redirect) => {
if (!settings.whiteListSettings) {
if (/\.css(\?.*)?$/.test(req.originalUrl)) {
return res.status(200).send('');
} else
if (/\.js(\?.*)?$/.test(req.originalUrl)) {
// return always valid js file for js, because if cache is active it leads to errors
const parts = req.originalUrl.split('/');
parts.shift();
const ref = parts.join('/');
// if request for web/lib, ignore it, because no redirect information
if (parts[0] === 'lib') return res.status(200).send('');
return res.status(200).send('document.location="/login/index.html?href=" + encodeURI(location.href.replace(location.origin, ""));');
} else {
return res.redirect(redirect);
}
}
let remoteIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
let whiteListIp = server.io.getWhiteListIpForAddress(remoteIp, settings.whiteListSettings);
adapter.log.silly('whiteListIp ' + whiteListIp);
if (!whiteListIp || settings.whiteListSettings[whiteListIp].user === 'auth') {
if (/\.css(\?.*)?$/.test(req.originalUrl)) {
return res.status(200).send('');
} else if (/\.js(\?.*)?$/.test(req.originalUrl)) {
// return always valid js file for js, because if cache is active it leads to errors
let parts = req.originalUrl.split('/');
parts.shift();
const ref = parts.join('/');
if (parts[0] === 'lib') return res.status(200).send('');
return res.status(200).send('document.location="/login/index.html?href=" + encodeURI(location.href.replace(location.origin, ""));');
} else {
return res.redirect(redirect);
}
}
req.logIn(settings.whiteListSettings[whiteListIp].user, err => {
return next(err);
});
};
server.app.post('/login', (req, res, next) => {
let redirect = '/';
let parts;
if (req.body.origin) {
parts = req.body.origin.split('=');
if (parts[1]) redirect = decodeURIComponent(parts[1]);
}
if (req.body && req.body.username && settings.addUserName && redirect.indexOf('?') === -1) {
parts = redirect.split('#');
parts[0] += '?' + req.body.username;
redirect = parts.join('#');
}
let authenticate = passport.authenticate('local', {
successRedirect: redirect,
failureRedirect: '/login/index.html' + req.body.origin + (req.body.origin ? '&error' : '?error'),
failureFlash: 'Invalid username or password.'
})(req, res, next);
});
server.app.get('/logout', (req, res) => {
req.logout();
res.redirect('/login/index.html');
});
// route middleware to make sure a user is logged in
server.app.use((req, res, next) => {
// if cache.manifest got back not 200 it makes an error
if (req.isAuthenticated() ||
/web\.\d+\/login-bg\.png(\?.*)?$/.test(req.originalUrl) ||
/cache\.manifest(\?.*)?$/.test(req.originalUrl) ||
/^\/login\//.test(req.originalUrl) ||
/\.ico(\?.*)?$/.test(req.originalUrl)
) {
return next();
}
autoLogonOrRedirectToLogin(req, res, next, '/login/index.html?href=' + encodeURIComponent(req.originalUrl));
});
} else {
server.app.get('/login', (req, res) => {
res.redirect('/');
});
server.app.get('/logout', (req, res) => {
res.redirect('/');
});
if (settings.whiteListEnabled) {
initAuth(server, settings);
server.app.use((req, res, next) => {
let remoteIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
let whiteListIp = server.io.getWhiteListIpForAddress(remoteIp, settings.whiteListSettings);
adapter.log.silly('whiteListIp ' + whiteListIp);
if (whiteListIp) {
req.logIn(settings.whiteListSettings[whiteListIp].user, err => {
return next(err);
});
} else {
req.logIn(settings.defaultUser, err => {
return next(err);
});
}
});
}
}
// Init read from states
server.app.get('/state/*', (req, res) => {
try {
const fileName = req.url.split('/', 3)[2].split('?', 2);
adapter.getForeignObject(fileName[0], (err, obj) => {
let contentType = 'text/plain';
if (obj && obj.common.type === 'file') {
contentType = mime.lookup(fileName[0]);
}
adapter.getBinaryState(fileName[0], {user: req.user ? 'system.user.' + req.user : settings.defaultUser}, (err, obj) => {
if (!err && obj !== null && obj !== undefined) {
if (obj && typeof obj === 'object' && obj.val !== undefined && obj.ack !== undefined) {
res.set('Content-Type', 'application/json');
} else {
res.set('Content-Type', contentType || 'text/plain');
}
res.status(200).send(obj);
} else {
res.status(404).send('404 Not found. File ' + fileName[0] + ' not found');
}
});
});
} catch (e) {
res.status(500).send('500. Error' + e);
}
});
server.app.get('*/_socket/info.js', (req, res) => {
res.set('Content-Type', 'application/javascript');
res.status(200).send(getInfoJs(settings));
});
// Enable CORS
if (settings.socketio) {
server.app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, *');
// intercept OPTIONS method
if ('OPTIONS' === req.method) {
res.status(200).send(200);
} else {
next();
}
});
}
let appOptions = {};
if (settings.cache) appOptions.maxAge = 30758400000;
server.server = LE.createServer(server.app, settings, settings.certificates, settings.leConfig, adapter.log);
server.server.__server = server;
} else {
adapter.log.error('port missing');
process.exit(1);
}
if (server.server) {
settings.port = parseInt(settings.port, 10);
adapter.getPort(settings.port, port => {
port = parseInt(port, 10);
if (port !== settings.port && !settings.findNextPort) {
adapter.log.error('port ' + settings.port + ' already in use');
process.exit(1);
}
server.server.listen(port, (!settings.bind || settings.bind === '0.0.0.0') ? undefined : settings.bind || undefined);
adapter.log.info('http' + (settings.secure ? 's' : '') + ' server listening on port ' + port);
});
}
// activate extensions
for (let e in extensions) {
if (!extensions.hasOwnProperty(e)) continue;
try {
// for debug purposes try to load file in current directory "/lib/file.js" (elsewise node.js cannot debug it)
let parts = extensions[e].path.split('/');
parts.shift();
let extAPI;
if (fs.existsSync(__dirname + '/' + parts.join('/'))) {
extAPI = require(__dirname + '/' + parts.join('/'));
} else {
extAPI = require(utils.appName + '.' + extensions[e].path);
}
extensions[e].obj = new extAPI(server.server, {secure: settings.secure, port: settings.port}, adapter, extensions[e].config, server.app);
adapter.log.info('Connect extension "' + extensions[e].path + '"');
} catch (err) {
adapter.log.error('Cannot start extension "' + e + '": ' + err);
}
}
// Activate integrated simple API
if (settings.simpleapi) {
let SimpleAPI = require(utils.appName + '.simple-api/lib/simpleapi.js');
server.api = new SimpleAPI(server.server, {secure: settings.secure, port: settings.port}, adapter);
}
// Activate integrated socket
if (ownSocket) {
let IOSocket = require(utils.appName + '.socketio/lib/socket.js');
let socketSettings = JSON.parse(JSON.stringify(settings));
// Authentication checked by server itself
socketSettings.auth = settings.auth;
socketSettings.secret = secret;
socketSettings.store = store;
socketSettings.ttl = settings.ttl || 3600;
socketSettings.forceWebSockets = settings.forceWebSockets || false;
server.io = new IOSocket(server.server, socketSettings, adapter);
}
if (server.app) {
// deliver web files from objectDB
server.app.use('/', (req, res) => {
let url = decodeURI(req.url);
// remove all ../
// important: Linux does not normalize "\" but fs.readFile accepts it as '/'
url = path.normalize(url.replace(/\\/g, '/')).replace(/\\/g, '/');
// remove '////' at start and let only one
if (url[0] === '/' && url[1] === '/') {
let i = 2;
while (url[i] === '/') i++;
url = url.substring(i - 1);
}
if ((url[0] === '.' && url[1] === '.') || (url[0] === '/' && url[1] === '.' && url[2] === '.')) {
res.status(404).send('Not found');
return;
}
if (server.api && server.api.checkRequest(url)) {
server.api.restApi(req, res);
return;
}
if (url === '/' || url === '/index.html') {
getListOfAllAdapters((err, data) => {
if (err) {
res.status(500).send('500. Error' + e);
} else {
res
.set('Content-Type', 'text/html')
.status(200)
.send(data);
}
});
return;
}
// add index.html
url = url.replace(/\/($|\?|#)/, '/index.html$1');
if (url.match(/^\/adapter\//)) {
// add .admin to adapter name
url = url.replace(/^\/adapter\/([a-zA-Z0-9-_]+)\//, '/$1.admin/');
}
if (url.match(/^\/lib\//)) {
url = '/' + adapter.name + url;
}
if (url.match(/^\/admin\//)) {
url = '/' + adapter.name + url;
}
url = url.split('/');
// Skip first /
url.shift();
// Get ID
let id = url.shift();
let versionPrefix = url[0];
url = url.join('/');
let pos = url.indexOf('?');
let noFileCache;
if (pos !== -1) {
url = url.substring(0, pos);
// disable file cache if request like /vis/files/picture.png?noCache
noFileCache = true;
}
// get adapter name
if (webByVersion[id]) {
if (!versionPrefix.match(/^\d+\.\d+.\d+$/)) {
// redirect to version
res.set('location', '/' + id + '/' + webByVersion[id] + '/' + url);
res.status(301).send();
return;
}
}
if (settings.cache && cache[id + '/' + url] && !noFileCache) {
res.contentType(cache[id + '/' + url].mimeType);
res.status(200).send(cache[id + '/' + url].buffer);
} else {
if (id === 'login' && url === 'index.html') {
loginPage = loginPage || prepareLoginTemplate();
let buffer = loginPage;
if (buffer === null || buffer === undefined) {
res.contentType('text/html');
res.status(200).send('File ' + url + ' not found', 404);
} else {
// Store file in cache
if (settings.cache) {
cache[id + '/' + url] = {buffer: buffer.toString(), mimeType: 'text/html'};
}
res.contentType('text/html');
res.status(200).send(buffer.toString());
}
} else {
// special solution for socket.io
if (socketIoFile !== false && url.match(/\/socket\.io\.js(\?.*)?$/)) {
if (socketIoFile) {
res.contentType('text/javascript');
res.status(200).send(socketIoFile);
return
} else {
try {
const dir = require.resolve('socket.io-client');
const fileDir = path.join(path.dirname(dir), '../dist/');
if (fs.existsSync(fileDir + 'socket.io.min.js')) {
socketIoFile = fs.readFileSync(fileDir + 'socket.io.min.js');
} else {
socketIoFile = fs.readFileSync(fileDir + 'socket.io.js');
}
} catch (e) {
socketIoFile = false;
}
if (socketIoFile) {
res.contentType('text/javascript');
res.status(200).send(socketIoFile);
return
}
}
}
adapter.readFile(id, webByVersion[id] ? url.substring(versionPrefix.length + 1) : url , {user: req.user ? 'system.user.' + req.user : settings.defaultUser, noFileCache: noFileCache}, (err, buffer, mimeType) => {
if (buffer === null || buffer === undefined || err) {
res.contentType('text/html');
res.status(404).send('File ' + url + ' not found: ' + err);
} else {
mimeType = mimeType || mime.lookup(url) || 'text/javascript';
// Store file in cache
if (settings.cache) {
cache[id + '/' + url] = {
buffer: buffer,
mimeType: mimeType
};
}
res.contentType(mimeType);
res.status(200).send(buffer);
}
});
}
}
});
}
if (server.server) {
return server;
} else {
return null;
}
}