UNPKG

jsharmony-cms

Version:
322 lines (278 loc) 11.7 kB
/* Copyright 2021 apHarmony This file is part of jsHarmony. jsHarmony is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. jsHarmony is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this package. If not, see <http://www.gnu.org/licenses/>. */ var _ = require('lodash'); var async = require('async'); var fs = require('fs'); var os = require('os'); var path = require('path'); var express = require('jsharmony/lib/express'); var cookieParser = require('jsharmony/lib/cookie-parser'); var compression = require('jsharmony/lib/compression'); var https = require('https'); var http = require('http'); var webpath = require('path').posix; var fspath = require('path'); var Helper = require('jsharmony/Helper'); var HelperFS = require('jsharmony/HelperFS'); function jsHarmonyCMSPreviewServer(cms){ this.cms = cms; this.jsh = cms.jsh; } jsHarmonyCMSPreviewServer.prototype.Run = function(run_cb){ if(!run_cb) run_cb = function(){}; var _this = this; var jsh = _this.jsh; var config = jsh.Config; var cms = _this.cms; var port = cms.Config.preview_server.serverPort; var siteConfig = jsh.Sites['main']; var isHTTPS = !!config.server.https_key; if(cms.Config.preview_server.serverHttpsKey === false) isHTTPS = false; if(isHTTPS){ var https_options; if(cms.Config.preview_server.serverHttpsKey){ https_options = { key: fs.readFileSync(cms.Config.preview_server.serverHttpsKey), cert: fs.readFileSync(cms.Config.preview_server.serverHttpsCert), }; if(cms.Config.preview_server.serverHttpsCa) https_options.ca = fs.readFileSync(cms.Config.preview_server.serverHttpsCa); } else { https_options = { key: fs.readFileSync(config.server.https_key), cert: fs.readFileSync(config.server.https_cert), }; if(config.server.https_ca) https_options.ca = fs.readFileSync(config.server.https_ca); } } if(!_this.cms.Config.preview_server.serverIp){ jsh.Log.error('Invalid CMS Config: Preview Server is enabled, but missing cms.Config.preview_server.serverIp'); return run_cb(); } var app = express(); app.use(compression()); app.jsh = jsh; function setNoCache(req, res){ if (req.headers['user-agent'] && req.headers['user-agent'].match(/Trident\//)) { //Add Cache header for IE res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); } } if(siteConfig.cookiesalt) app.use('/', cookieParser(siteConfig.cookiesalt, { path: siteConfig.baseurl })); else app.use('/', cookieParser({ path: siteConfig.baseurl })); app.use(_this.jsh.Log.express.with({ pathPrefix: 'PREVIEWSERVER:' })); app.set('view engine', 'ejs'); app.all('*', function (req, res, next) { req.jshsite = siteConfig; req.baseurl = cms.getCmsBaseUrlFromReq(req); //Delete jQuery Anti-Cache timestamp if('_' in req.query) delete req.query['_']; setNoCache(req,res); res.setHeader('X-UA-Compatible','IE=edge'); //Cookie Suffix port = req.socket.localPort; req.jsh_cookie_suffix = '_'+(jsh.Config.server.https_port||jsh.Config.server.http_port)+'_'+(siteConfig.id||''); return next(); }); app.all('*', function(req, res, next){ cms.funcs.validateClientToken(req, res, next, ['get','options'], { }, function(){ return next(); }); }); //Authenticate the user app.all('*', function (req, res, next) { if(!siteConfig.auth){ return jsh.Auth.NoAuth(req, res, next); } //Handle Authentication (siteConfig.auth.onAuth || jsh.Auth).call(jsh, req, res, next, function (errno, msg) { res.end('<html><body>Please log in at <a href="'+Helper.escapeHTML(req.baseurl)+'">'+Helper.escapeHTML(req.baseurl)+'</a></body></html>'); }); }); //Get the current site ID app.all('*', function (req, res, next) { jsh.AppSrv.ExecRow(req._DBContext, cms.funcs.replaceSchema('select site_id, site_name from {schema}.v_my_site where site_id={schema}.my_current_site_id()'), [], {}, function (err, rslt) { if(err) return Helper.GenError(req, res, -99999, err.toString()); if (!rslt || !rslt.length || !rslt[0] || !rslt[0].site_id){ //If no site checked out - show error message var url = req.baseurl+cms.namespace+'Site_Listing'; return res.end('<html><body>Please checkout a CMS site: <a href="'+Helper.escapeHTML(url)+'">'+Helper.escapeHTML(url)+'</a></body></html>'); } req.site_id = rslt[0].site_id; req.site_name = rslt[0].site_name; return next(); }); }); //Websnippets Listing app.get('/templates/websnippets/?', function(req, res, next){ var rslt = []; //{"title": "Item 1", "description": "Long Description", "url": "/templates/websnippets/item1.html", "content": "..."} var basepath = fspath.join(jsh.Config.datadir,'site',req.site_id.toString(), 'templates/websnippets'); async.waterfall([ //Recursively read all web snippets in folder and combine into file function(gen_cb){ HelperFS.funcRecursive(basepath, function(filepath, filerelativepath, file_cb){ var ext = path.extname(filepath); //Only read HTML files if(!_.includes(['.html','.htm'], ext)) return file_cb(); fs.readFile(filepath, 'utf8', function(err, content){ if(err) return file_cb(err); //Parse each web snippet + extract <script type="text/cms-websnippet-config"> var templateParts = null; try{ templateParts = cms.funcs.parseConfig(content, 'cms-websnippet-config', 'websnippet config "' + filerelativepath + '"', { extractFromContent: true }); } catch(ex){ return file_cb(new Error('Could not parse web snippet "' + filerelativepath+'": '+ex.toString())); } //Add websnippet to array filerelativepath = HelperFS.convertWindowsToPosix(filerelativepath); var templateConfig = { title: filerelativepath, description: '', content: templateParts.content }; if(templateParts.config.url) delete templateConfig.content; _.extend(templateConfig, templateParts.config); rslt.push(templateConfig); return file_cb(); }); }, null, null, function(err){ return gen_cb(err); } ); }, ], function(err){ if(err) return Helper.GenError(req, res, -99999, err.toString()); res.end(JSON.stringify(rslt)); }); }); //Return file from local site app.all('*', function (req, res, next) { var rpath = req.path; var sitepath = fspath.join(jsh.Config.datadir,'site',req.site_id.toString()); try{ rpath = webpath.normalize(rpath); } catch(ex){ return next(); } var syspath = ''; var foundFile = false; async.waterfall([ //File Match function(path_cb){ syspath = fspath.join(sitepath, rpath); fs.realpath(syspath, function(err, fullpath){ if(err) return next(); if(fullpath == sitepath) return path_cb(); if(fullpath.indexOf(sitepath + fspath.sep)!=0) return next(); fs.lstat(syspath, function(err, stats){ if(err) return next(); if(stats.isDirectory()) return path_cb(); if(stats.isFile()){ foundFile = true; return path_cb(); } return next(); }); }); }, //Directory Match function(path_cb){ if(foundFile) return path_cb(); syspath = fspath.join(syspath, 'index.html'); fs.lstat(syspath, function(err, stats){ if(err){ res.end('No index.html file found in this folder'); return; } if(stats.isFile()){ foundFile = true; return path_cb(); } return next(); }); }, //Return file function(path_cb){ //Process templates if(rpath.indexOf('/templates/pages/')==0){ //Only process HTML files var ext = path.extname(syspath); if(_.includes(['.html','.htm'], ext)){ fs.readFile(syspath, 'utf8', function(err, data){ if(err) return next(); cms.funcs.getTemplateVariables('current', req._DBContext, 'editor', {}, {}, {}, function(err, template_variables){ if(err) return res.end('Error loading template variables'); var fcontent = ''; try{ fcontent = cms.funcs.generateEditorTemplate(data, { cmsBaseUrl: req.baseurl, template_variables: template_variables }); } catch(ex){ res.end('Error loading template: '+ex.toString()); return; } res.end(fcontent); }); }); return; } } HelperFS.outputFile(req, res, syspath, '', undefined, { attachment: false }); }, ], function(err){ //404 Error return next(); }); }); app.all('*', function (req, res, next) { jsh.Gen404(req, res, { view: 'jsHarmonyCMS.PreviewServer.404', renderParams: { site_name: req.site_name } }); }); //Error Handler app.use(function (err, req, res, next) { var errorpage = 'error'; if (req.jshsite && req.jshsite.show_system_errors) errorpage = 'error_debug'; jsh.Log.error(err); jsh.Log.info(err.stack); res.status(err.status || 500); res.render(jsh.getView(req, errorpage, { disable_override: true }), { message: err.message, error: err, }); }); //Launch Server var server = (isHTTPS?https:http).createServer(https_options, app); server.on('listening',function(){ jsh.Log.info('Preview Server listening on '+(isHTTPS?'HTTPS':'HTTP')+' port '+port); return run_cb(); }); server.listen(port, cms.Config.preview_server.serverIp); }; jsHarmonyCMSPreviewServer.prototype.getURL = function(hostname){ var _this = this; if(_this.cms.Config.preview_server.serverUrl) return _this.cms.Config.preview_server.serverUrl; var isHTTPS = !!_this.jsh.Config.server.https_key; if(_this.cms.Config.preview_server.serverHttpsKey === false) isHTTPS = false; var server_txt = _this.cms.Config.preview_server.serverIp; var server_port = _this.cms.Config.preview_server.serverPort; var server_scheme = isHTTPS ? 'https://' : 'http://'; if(hostname && server_port) return server_scheme+hostname+':'+server_port; if(server_txt == '0.0.0.0') server_txt = os.hostname().toLowerCase(); if(server_txt && server_port) return server_scheme+server_txt+':'+server_port; return ''; }; module.exports = exports = jsHarmonyCMSPreviewServer;