diff --git a/src/node/db/DB.js b/src/node/db/DB.js index aca8d7432..601c08c5c 100644 --- a/src/node/db/DB.js +++ b/src/node/db/DB.js @@ -1,3 +1,5 @@ +'use strict'; + /** * The DB Module provides a database initalized with the settings * provided by the settings module @@ -25,7 +27,8 @@ const log4js = require('log4js'); const util = require('util'); // set database settings -const db = new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger('ueberDB')); +const db = + new ueberDB.database(settings.dbType, settings.dbSettings, null, log4js.getLogger('ueberDB')); /** * The UeberDB Object that provides the database functions @@ -36,41 +39,38 @@ exports.db = null; * Initalizes the database with the settings provided by the settings module * @param {Function} callback */ -exports.init = function () { - // initalize the database async - return new Promise((resolve, reject) => { - db.init((err) => { - if (err) { - // there was an error while initializing the database, output it and stop - console.error('ERROR: Problem while initalizing the database'); - console.error(err.stack ? err.stack : err); - process.exit(1); - } +exports.init = async () => await new Promise((resolve, reject) => { + db.init((err) => { + if (err) { + // there was an error while initializing the database, output it and stop + console.error('ERROR: Problem while initalizing the database'); + console.error(err.stack ? err.stack : err); + process.exit(1); + } - // everything ok, set up Promise-based methods - ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach((fn) => { - exports[fn] = util.promisify(db[fn].bind(db)); - }); - - // set up wrappers for get and getSub that can't return "undefined" - const get = exports.get; - exports.get = async function (key) { - const result = await get(key); - return (result === undefined) ? null : result; - }; - - const getSub = exports.getSub; - exports.getSub = async function (key, sub) { - const result = await getSub(key, sub); - return (result === undefined) ? null : result; - }; - - // exposed for those callers that need the underlying raw API - exports.db = db; - resolve(); + // everything ok, set up Promise-based methods + ['get', 'set', 'findKeys', 'getSub', 'setSub', 'remove', 'doShutdown'].forEach((fn) => { + exports[fn] = util.promisify(db[fn].bind(db)); }); + + // set up wrappers for get and getSub that can't return "undefined" + const get = exports.get; + exports.get = async (key) => { + const result = await get(key); + return (result === undefined) ? null : result; + }; + + const getSub = exports.getSub; + exports.getSub = async (key, sub) => { + const result = await getSub(key, sub); + return (result === undefined) ? null : result; + }; + + // exposed for those callers that need the underlying raw API + exports.db = db; + resolve(); }); -}; +}); exports.shutdown = async (hookName, context) => { await exports.doShutdown(); diff --git a/src/node/hooks/express.js b/src/node/hooks/express.js index 05283b5cb..d520ee6eb 100644 --- a/src/node/hooks/express.js +++ b/src/node/hooks/express.js @@ -1,3 +1,5 @@ +'use strict'; + const _ = require('underscore'); const cookieParser = require('cookie-parser'); const express = require('express'); @@ -5,9 +7,7 @@ const expressSession = require('express-session'); const fs = require('fs'); const hooks = require('../../static/js/pluginfw/hooks'); const log4js = require('log4js'); -const npm = require('npm/lib/npm'); -const path = require('path'); -const sessionStore = require('../db/SessionStore'); +const SessionStore = require('../db/SessionStore'); const settings = require('../utils/Settings'); const stats = require('../stats'); const util = require('util'); @@ -36,13 +36,16 @@ exports.createServer = async () => { if (!_.isEmpty(settings.users)) { console.log(`The plugin admin page is at http://${settings.ip}:${settings.port}/admin/plugins`); } else { - console.warn("Admin username and password not set in settings.json. To access admin please uncomment and edit 'users' in settings.json"); + console.warn('Admin username and password not set in settings.json. ' + + 'To access admin please uncomment and edit "users" in settings.json'); } const env = process.env.NODE_ENV || 'development'; if (env !== 'production') { - console.warn('Etherpad is running in Development mode. This mode is slower for users and less secure than production mode. You should set the NODE_ENV environment variable to production by using: export NODE_ENV=production'); + console.warn('Etherpad is running in Development mode. This mode is slower for users and ' + + 'less secure than production mode. You should set the NODE_ENV environment ' + + 'variable to production by using: export NODE_ENV=production'); } }; @@ -138,7 +141,7 @@ exports.restartServer = async () => { exports.sessionMiddleware = expressSession({ secret: settings.sessionKey, - store: new sessionStore(), + store: new SessionStore(), resave: false, saveUninitialized: true, // Set the cookie name to a javascript identifier compatible string. Makes code handling it diff --git a/src/node/hooks/express/adminsettings.js b/src/node/hooks/express/adminsettings.js index d5fffcbf2..139cce1b1 100644 --- a/src/node/hooks/express/adminsettings.js +++ b/src/node/hooks/express/adminsettings.js @@ -1,9 +1,11 @@ -const eejs = require('ep_etherpad-lite/node/eejs'); -const settings = require('ep_etherpad-lite/node/utils/Settings'); -const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); -const fs = require('fs'); +'use strict'; -exports.expressCreateServer = function (hook_name, args, cb) { +const eejs = require('../../eejs'); +const fs = require('fs'); +const hooks = require('../../../static/js/pluginfw/hooks'); +const settings = require('../../utils/Settings'); + +exports.expressCreateServer = (hookName, args, cb) => { args.app.get('/admin/settings', (req, res) => { res.send(eejs.require('ep_etherpad-lite/templates/admin/settings.html', { req, @@ -14,10 +16,11 @@ exports.expressCreateServer = function (hook_name, args, cb) { return cb(); }; -exports.socketio = function (hook_name, args, cb) { +exports.socketio = (hookName, args, cb) => { const io = args.io.of('/settings'); io.on('connection', (socket) => { - if (!socket.conn.request.session || !socket.conn.request.session.user || !socket.conn.request.session.user.is_admin) return; + const {session: {user: {is_admin: isAdmin} = {}} = {}} = socket.conn.request; + if (!isAdmin) return; socket.on('load', (query) => { fs.readFile('settings.json', 'utf8', (err, data) => { diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index 3a0cd1bb0..3ca2f41aa 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -1,13 +1,15 @@ +'use strict'; + const express = require('../express'); const proxyaddr = require('proxy-addr'); const settings = require('../../utils/Settings'); const socketio = require('socket.io'); const socketIORouter = require('../../handler/SocketIORouter'); -const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const hooks = require('../../../static/js/pluginfw/hooks'); const padMessageHandler = require('../../handler/PadMessageHandler'); -exports.expressCreateServer = function (hook_name, args, cb) { +exports.expressCreateServer = (hookName, args, cb) => { // init socket.io and redirect all requests to the MessageHandler // there shouldn't be a browser that isn't compatible to all // transports in this list at once diff --git a/src/node/server.js b/src/node/server.js index 5b3040bfc..3219f5185 100755 --- a/src/node/server.js +++ b/src/node/server.js @@ -1,4 +1,7 @@ #!/usr/bin/env node + +'use strict'; + /** * This module is started with bin/run.sh. It sets up a Express HTTP and a Socket.IO Server. * Static file Requests are answered directly from this module, Socket.IO messages are passed @@ -35,9 +38,9 @@ NodeVersion.checkDeprecationStatus('10.13.0', '1.8.3'); const UpdateCheck = require('./utils/UpdateCheck'); const db = require('./db/DB'); const express = require('./hooks/express'); -const hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks'); +const hooks = require('../static/js/pluginfw/hooks'); const npm = require('npm/lib/npm.js'); -const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); +const plugins = require('../static/js/pluginfw/plugins'); const settings = require('./utils/Settings'); const util = require('util'); diff --git a/src/node/stats.js b/src/node/stats.js index 8f0d1f180..cecaca20d 100644 --- a/src/node/stats.js +++ b/src/node/stats.js @@ -1,3 +1,5 @@ +'use strict'; + const measured = require('measured-core'); module.exports = measured.createCollection(); diff --git a/src/node/utils/Minify.js b/src/node/utils/Minify.js index 368e682c1..66a1926a9 100644 --- a/src/node/utils/Minify.js +++ b/src/node/utils/Minify.js @@ -1,3 +1,5 @@ +'use strict'; + /** * This Module manages all /minified/* requests. It controls the * minification && compression of Javascript and CSS. @@ -23,10 +25,8 @@ const ERR = require('async-stacktrace'); const settings = require('./Settings'); const async = require('async'); const fs = require('fs'); -const StringDecoder = require('string_decoder').StringDecoder; -const CleanCSS = require('clean-css'); const path = require('path'); -const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); +const plugins = require('../../static/js/pluginfw/plugin_defs'); const RequireKernel = require('etherpad-require-kernel'); const urlutil = require('url'); const mime = require('mime-types'); @@ -39,7 +39,7 @@ const ROOT_DIR = path.normalize(`${__dirname}/../../static/`); const TAR_PATH = path.join(__dirname, 'tar.json'); const tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8')); -const threadsPool = Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2); +const threadsPool = new Threads.Pool(() => Threads.spawn(new Threads.Worker('./MinifyWorker')), 2); const LIBRARY_WHITELIST = [ 'async', @@ -53,30 +53,28 @@ const LIBRARY_WHITELIST = [ // Rewrite tar to include modules with no extensions and proper rooted paths. const LIBRARY_PREFIX = 'ep_etherpad-lite/static/js'; exports.tar = {}; -function prefixLocalLibraryPath(path) { - if (path.charAt(0) == '$') { +const prefixLocalLibraryPath = (path) => { + if (path.charAt(0) === '$') { return path.slice(1); } else { return `${LIBRARY_PREFIX}/${path}`; } -} - -for (const key in tar) { - exports.tar[prefixLocalLibraryPath(key)] = - tar[key].map(prefixLocalLibraryPath).concat( - tar[key].map(prefixLocalLibraryPath).map((p) => p.replace(/\.js$/, '')) - ).concat( - tar[key].map(prefixLocalLibraryPath).map((p) => `${p.replace(/\.js$/, '')}/index.js`) - ); +}; +for (const [key, relativeFiles] of Object.entries(tar)) { + const files = relativeFiles.map(prefixLocalLibraryPath); + exports.tar[prefixLocalLibraryPath(key)] = files + .concat(files.map((p) => p.replace(/\.js$/, ''))) + .concat(files.map((p) => `${p.replace(/\.js$/, '')}/index.js`)); } // What follows is a terrible hack to avoid loop-back within the server. // TODO: Serve files from another service, or directly from the file system. -function requestURI(url, method, headers, callback) { +const requestURI = (url, method, headers, callback) => { const parsedURL = urlutil.parse(url); - let status = 500; var headers = {}; const - content = []; + let status = 500; + var headers = {}; + const content = []; const mockRequest = { url, @@ -85,7 +83,7 @@ function requestURI(url, method, headers, callback) { headers, }; const mockResponse = { - writeHead(_status, _headers) { + writeHead: (_status, _headers) => { status = _status; for (const header in _headers) { if (Object.prototype.hasOwnProperty.call(_headers, header)) { @@ -93,61 +91,60 @@ function requestURI(url, method, headers, callback) { } } }, - setHeader(header, value) { + setHeader: (header, value) => { headers[header.toLowerCase()] = value.toString(); }, - header(header, value) { + header: (header, value) => { headers[header.toLowerCase()] = value.toString(); }, - write(_content) { + write: (_content) => { _content && content.push(_content); }, - end(_content) { + end: (_content) => { _content && content.push(_content); callback(status, headers, content.join('')); }, }; minify(mockRequest, mockResponse); -} -function requestURIs(locations, method, headers, callback) { +}; + +const requestURIs = (locations, method, headers, callback) => { let pendingRequests = locations.length; const responses = []; - function respondFor(i) { - return function (status, headers, content) { - responses[i] = [status, headers, content]; - if (--pendingRequests == 0) { - completed(); - } - }; - } - - for (let i = 0, ii = locations.length; i < ii; i++) { - requestURI(locations[i], method, headers, respondFor(i)); - } - - function completed() { + const completed = () => { const statuss = responses.map((x) => x[0]); const headerss = responses.map((x) => x[1]); const contentss = responses.map((x) => x[2]); callback(statuss, headerss, contentss); + }; + + const respondFor = (i) => (status, headers, content) => { + responses[i] = [status, headers, content]; + if (--pendingRequests === 0) { + completed(); + } + }; + + for (let i = 0, ii = locations.length; i < ii; i++) { + requestURI(locations[i], method, headers, respondFor(i)); } -} +}; /** * creates the minifed javascript for the given minified name * @param req the Express request * @param res the Express response */ -function minify(req, res) { +const minify = (req, res) => { let filename = req.params.filename; // No relative paths, especially if they may go up the file hierarchy. filename = path.normalize(path.join(ROOT_DIR, filename)); filename = filename.replace(/\.\./g, ''); - if (filename.indexOf(ROOT_DIR) == 0) { + if (filename.indexOf(ROOT_DIR) === 0) { filename = filename.slice(ROOT_DIR.length); filename = filename.replace(/\\/g, '/'); } else { @@ -161,7 +158,7 @@ function minify(req, res) { are rewritten into ROOT_PATH_OF_MYPLUGIN/static/js/test.js, commonly ETHERPAD_ROOT/node_modules/ep_myplugin/static/js/test.js */ - const match = filename.match(/^plugins\/([^\/]+)(\/(?:(static\/.*)|.*))?$/); + const match = filename.match(/^plugins\/([^/]+)(\/(?:(static\/.*)|.*))?$/); if (match) { const library = match[1]; const libraryPath = match[2] || ''; @@ -171,7 +168,7 @@ function minify(req, res) { const pluginPath = plugin.package.realPath; filename = path.relative(ROOT_DIR, pluginPath + libraryPath); filename = filename.replace(/\\/g, '/'); // windows path fix - } else if (LIBRARY_WHITELIST.indexOf(library) != -1) { + } else if (LIBRARY_WHITELIST.indexOf(library) !== -1) { // Go straight into node_modules // Avoid `require.resolve()`, since 'mustache' and 'mustache/index.js' // would end up resolving to logically distinct resources. @@ -203,11 +200,11 @@ function minify(req, res) { } else if (new Date(req.headers['if-modified-since']) >= date) { res.writeHead(304, {}); res.end(); - } else if (req.method == 'HEAD') { + } else if (req.method === 'HEAD') { res.header('Content-Type', contentType); res.writeHead(200, {}); res.end(); - } else if (req.method == 'GET') { + } else if (req.method === 'GET') { getFileCompressed(filename, contentType, (error, content) => { if (ERR(error, () => { res.writeHead(500, {}); @@ -223,10 +220,10 @@ function minify(req, res) { res.end(); } }, 3); -} +}; // find all includes in ace.js and embed them. -function getAceFile(callback) { +const getAceFile = (callback) => { fs.readFile(`${ROOT_DIR}js/ace.js`, 'utf8', (err, data) => { if (ERR(err, callback)) return; @@ -256,10 +253,10 @@ function getAceFile(callback) { resourceURI = resourceURI.replace(/\\/g, '/'); // Windows (safe generally?) requestURI(resourceURI, 'GET', {}, (status, headers, body) => { - const error = !(status == 200 || status == 404); + const error = !(status === 200 || status === 404); if (!error) { data += `Ace2Editor.EMBEDED[${JSON.stringify(filename)}] = ${ - JSON.stringify(status == 200 ? body || '' : null)};\n`; + JSON.stringify(status === 200 ? body || '' : null)};\n`; } else { console.error(`getAceFile(): error getting ${resourceURI}. Status code: ${status}`); } @@ -269,10 +266,10 @@ function getAceFile(callback) { callback(error, data); }); }); -} +}; // Check for the existance of the file and get the last modification date. -function statFile(filename, callback, dirStatLimit) { +const statFile = (filename, callback, dirStatLimit) => { /* * The only external call to this function provides an explicit value for * dirStatLimit: this check could be removed. @@ -281,20 +278,20 @@ function statFile(filename, callback, dirStatLimit) { dirStatLimit = 3; } - if (dirStatLimit < 1 || filename == '' || filename == '/') { + if (dirStatLimit < 1 || filename === '' || filename === '/') { callback(null, null, false); - } else if (filename == 'js/ace.js') { + } else if (filename === 'js/ace.js') { // Sometimes static assets are inlined into this file, so we have to stat // everything. lastModifiedDateOfEverything((error, date) => { callback(error, date, !error); }); - } else if (filename == 'js/require-kernel.js') { + } else if (filename === 'js/require-kernel.js') { callback(null, requireLastModified(), true); } else { fs.stat(ROOT_DIR + filename, (error, stats) => { if (error) { - if (error.code == 'ENOENT') { + if (error.code === 'ENOENT') { // Stat the directory instead. statFile(path.dirname(filename), (error, date, exists) => { callback(error, date, false); @@ -309,8 +306,9 @@ function statFile(filename, callback, dirStatLimit) { } }); } -} -function lastModifiedDateOfEverything(callback) { +}; + +const lastModifiedDateOfEverything = (callback) => { const folders2check = [`${ROOT_DIR}js/`, `${ROOT_DIR}css/`]; let latestModification = 0; // go trough this two folders @@ -343,23 +341,19 @@ function lastModifiedDateOfEverything(callback) { }, () => { callback(null, latestModification); }); -} +}; // This should be provided by the module, but until then, just use startup // time. const _requireLastModified = new Date(); -function requireLastModified() { - return _requireLastModified.toUTCString(); -} -function requireDefinition() { - return `var require = ${RequireKernel.kernelSource};\n`; -} +const requireLastModified = () => _requireLastModified.toUTCString(); +const requireDefinition = () => `var require = ${RequireKernel.kernelSource};\n`; -function getFileCompressed(filename, contentType, callback) { +const getFileCompressed = (filename, contentType, callback) => { getFile(filename, (error, content) => { if (error || !content || !settings.minify) { callback(error, content); - } else if (contentType == 'application/javascript') { + } else if (contentType === 'application/javascript') { threadsPool.queue(async ({compressJS}) => { try { logger.info('Compress JS file %s.', filename); @@ -373,12 +367,13 @@ function getFileCompressed(filename, contentType, callback) { content = compressResult.code.toString(); // Convert content obj code to string } } catch (error) { - console.error(`getFile() returned an error in getFileCompressed(${filename}, ${contentType}): ${error}`); + console.error('getFile() returned an error in ' + + `getFileCompressed(${filename}, ${contentType}): ${error}`); } callback(null, content); }); - } else if (contentType == 'text/css') { + } else if (contentType === 'text/css') { threadsPool.queue(async ({compressCSS}) => { try { logger.info('Compress CSS file %s.', filename); @@ -394,17 +389,17 @@ function getFileCompressed(filename, contentType, callback) { callback(null, content); } }); -} +}; -function getFile(filename, callback) { - if (filename == 'js/ace.js') { +const getFile = (filename, callback) => { + if (filename === 'js/ace.js') { getAceFile(callback); - } else if (filename == 'js/require-kernel.js') { + } else if (filename === 'js/require-kernel.js') { callback(undefined, requireDefinition()); } else { fs.readFile(ROOT_DIR + filename, callback); } -} +}; exports.minify = minify; diff --git a/src/static/js/admin/settings.js b/src/static/js/admin/settings.js index 3cca979a5..6ebc5336d 100644 --- a/src/static/js/admin/settings.js +++ b/src/static/js/admin/settings.js @@ -1,7 +1,8 @@ +'use strict'; + $(document).ready(() => { - let socket; const loc = document.location; - const port = loc.port == '' ? (loc.protocol == 'https:' ? 443 : 80) : loc.port; + const port = loc.port === '' ? (loc.protocol === 'https:' ? 443 : 80) : loc.port; const url = `${loc.protocol}//${loc.hostname}:${port}/`; const pathComponents = location.pathname.split('/'); // Strip admin/plugins @@ -10,7 +11,7 @@ $(document).ready(() => { // connect const room = `${url}settings`; - socket = io.connect(room, {path: `${baseURL}socket.io`, resource}); + const socket = io.connect(room, {path: `${baseURL}socket.io`, resource}); socket.on('settings', (settings) => { /* Check whether the settings.json is authorized to be viewed */ @@ -58,18 +59,13 @@ $(document).ready(() => { }); -function isJSONClean(data) { +const isJSONClean = (data) => { let cleanSettings = JSON.minify(data); // this is a bit naive. In theory some key/value might contain the sequences ',]' or ',}' cleanSettings = cleanSettings.replace(',]', ']').replace(',}', '}'); try { - var response = jQuery.parseJSON(cleanSettings); + return typeof jQuery.parseJSON(cleanSettings) === 'object'; } catch (e) { return false; // the JSON failed to be parsed } - if (typeof response !== 'object') { - return false; - } else { - return true; - } -} +}; diff --git a/tests/backend/common.js b/tests/backend/common.js index 887312809..22a84c097 100644 --- a/tests/backend/common.js +++ b/tests/backend/common.js @@ -1,14 +1,12 @@ -/* global __dirname, exports, require */ +'use strict'; -function m(mod) { return `${__dirname}/../../src/${mod}`; } - -const apiHandler = require(m('node/handler/APIHandler')); -const log4js = require(m('node_modules/log4js')); +const apiHandler = require('ep_etherpad-lite/node/handler/APIHandler'); +const log4js = require('ep_etherpad-lite/node_modules/log4js'); const process = require('process'); -const server = require(m('node/server')); -const settings = require(m('node/utils/Settings')); -const supertest = require(m('node_modules/supertest')); -const webaccess = require(m('node/hooks/express/webaccess')); +const server = require('ep_etherpad-lite/node/server'); +const settings = require('ep_etherpad-lite/node/utils/Settings'); +const supertest = require('ep_etherpad-lite/node_modules/supertest'); +const webaccess = require('ep_etherpad-lite/node/hooks/express/webaccess'); const backups = {}; let inited = false; diff --git a/tests/backend/specs/socketio.js b/tests/backend/specs/socketio.js index 00877faf9..6f3af2ee0 100644 --- a/tests/backend/specs/socketio.js +++ b/tests/backend/specs/socketio.js @@ -1,14 +1,12 @@ -/* global __dirname, __filename, afterEach, before, beforeEach, clearTimeout, describe, it, require, setTimeout */ - -function m(mod) { return `${__dirname}/../../../src/${mod}`; } +'use strict'; const assert = require('assert').strict; const common = require('../common'); -const io = require(m('node_modules/socket.io-client')); -const padManager = require(m('node/db/PadManager')); -const plugins = require(m('static/js/pluginfw/plugin_defs')); -const setCookieParser = require(m('node_modules/set-cookie-parser')); -const settings = require(m('node/utils/Settings')); +const io = require('ep_etherpad-lite/node_modules/socket.io-client'); +const padManager = require('ep_etherpad-lite/node/db/PadManager'); +const plugins = require('ep_etherpad-lite/static/js/pluginfw/plugin_defs'); +const setCookieParser = require('ep_etherpad-lite/node_modules/set-cookie-parser'); +const settings = require('ep_etherpad-lite/node/utils/Settings'); const logger = common.logger; @@ -50,7 +48,8 @@ const getSocketEvent = async (socket, event) => { const connect = async (res) => { // Convert the `set-cookie` header(s) into a `cookie` header. const resCookies = (res == null) ? {} : setCookieParser.parse(res, {map: true}); - const reqCookieHdr = Object.entries(resCookies).map(([name, cookie]) => `${name}=${encodeURIComponent(cookie.value)}`).join('; '); + const reqCookieHdr = Object.entries(resCookies).map( + ([name, cookie]) => `${name}=${encodeURIComponent(cookie.value)}`).join('; '); logger.debug('socket.io connecting...'); const socket = io(`${common.baseUrl}/`, {