mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-04-07 14:04:35 +08:00
add local plugins with check folder update
This commit is contained in:
@ -138,6 +138,10 @@
|
||||
"algorithm": "HS256",
|
||||
"expires": "5m"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"path": "../../../sdkjs-plugins",
|
||||
"url": "../../../../sdkjs-plugins"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
"config": "^1.21.0",
|
||||
"dnscache": "0.0.4",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"forwarded": "^0.1.0",
|
||||
"ipaddr.js": "^1.2.0",
|
||||
"jsonwebtoken": "^7.1.9",
|
||||
"log4js": "^0.6.38",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -50,11 +50,13 @@ const dnscache = require('dnscache')({
|
||||
const jwt = require('jsonwebtoken');
|
||||
const NodeCache = require( "node-cache" );
|
||||
const ms = require('ms');
|
||||
var constants = require('./constants');
|
||||
const constants = require('./constants');
|
||||
const forwarded = require('forwarded');
|
||||
|
||||
var configIpFilter = config.get('services.CoAuthoring.ipfilter');
|
||||
var cfgIpFilterRules = configIpFilter.get('rules');
|
||||
var cfgIpFilterErrorCode = configIpFilter.get('errorcode');
|
||||
const cfgIpFilterEseForRequest = configIpFilter.get('useforrequest');
|
||||
var cfgExpPemStdTtl = config.get('services.CoAuthoring.expire.pemStdTTL');
|
||||
var cfgExpPemCheckPeriod = config.get('services.CoAuthoring.expire.pemCheckPeriod');
|
||||
var cfgTokenOutboxHeader = config.get('services.CoAuthoring.token.outbox.header');
|
||||
@ -103,6 +105,7 @@ function fsStat(fsPath) {
|
||||
});
|
||||
});
|
||||
}
|
||||
exports.fsStat = fsStat;
|
||||
function fsReadDir(fsPath) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.readdir(fsPath, function(err, list) {
|
||||
@ -114,27 +117,40 @@ function fsReadDir(fsPath) {
|
||||
});
|
||||
});
|
||||
}
|
||||
function* walkDir(fsPath, results, optNoSubDir) {
|
||||
var list = yield fsReadDir(fsPath);
|
||||
for (var i = 0; i < list.length; ++i) {
|
||||
var fileName = list[i];
|
||||
var file = path.join(fsPath, fileName);
|
||||
var stats = yield fsStat(file);
|
||||
function* walkDir(fsPath, results, optNoSubDir, optOnlyFolders) {
|
||||
const list = yield fsReadDir(fsPath);
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
const file = path.join(fsPath, list[i]);
|
||||
const stats = yield fsStat(file);
|
||||
if (stats.isDirectory()) {
|
||||
if (optNoSubDir) {
|
||||
continue;
|
||||
optOnlyFolders && results.push(file);
|
||||
} else {
|
||||
yield* walkDir(file, results);
|
||||
yield* walkDir(file, results, optNoSubDir, optOnlyFolders);
|
||||
}
|
||||
} else {
|
||||
results.push(file);
|
||||
!optOnlyFolders && results.push(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.listFolders = function(fsPath, optNoSubDir) {
|
||||
return co(function* () {
|
||||
let stats, list = [];
|
||||
try {
|
||||
stats = yield fsStat(fsPath);
|
||||
} catch (e) {
|
||||
//exception if fsPath not exist
|
||||
stats = null;
|
||||
}
|
||||
if (stats && stats.isDirectory()) {
|
||||
yield* walkDir(fsPath, list, optNoSubDir, true);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
};
|
||||
exports.listObjects = function(fsPath, optNoSubDir) {
|
||||
return co(function* () {
|
||||
var list;
|
||||
var stats;
|
||||
let stats, list = [];
|
||||
try {
|
||||
stats = yield fsStat(fsPath);
|
||||
} catch (e) {
|
||||
@ -143,13 +159,10 @@ exports.listObjects = function(fsPath, optNoSubDir) {
|
||||
}
|
||||
if (stats) {
|
||||
if (stats.isDirectory()) {
|
||||
list = [];
|
||||
yield* walkDir(fsPath, list, optNoSubDir);
|
||||
yield* walkDir(fsPath, list, optNoSubDir, false);
|
||||
} else {
|
||||
list = [fsPath];
|
||||
list.push(fsPath);
|
||||
}
|
||||
} else {
|
||||
list = [];
|
||||
}
|
||||
return list;
|
||||
});
|
||||
@ -170,17 +183,6 @@ exports.readFile = function(file) {
|
||||
});
|
||||
});
|
||||
};
|
||||
exports.fsStat = function(file) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.stat(file, function(err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
function makeAndroidSafeFileName(str) {
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
if (-1 == ANDROID_SAFE_FILENAME.indexOf(str[i])) {
|
||||
@ -589,6 +591,20 @@ function checkIpFilter(ipString, opt_hostname) {
|
||||
return status;
|
||||
}
|
||||
exports.checkIpFilter = checkIpFilter;
|
||||
function checkClientIp(req, res, next) {
|
||||
let status = 0;
|
||||
if (cfgIpFilterEseForRequest) {
|
||||
const addresses = forwarded(req);
|
||||
const ipString = addresses[addresses.length - 1];
|
||||
status = checkIpFilter(ipString);
|
||||
}
|
||||
if (status > 0) {
|
||||
res.sendStatus(status);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
exports.checkClientIp = checkClientIp;
|
||||
function dnsLookup(hostname, options) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
dnscache.lookup(hostname, options, function(err, addresses){
|
||||
@ -655,3 +671,4 @@ function fillJwtForRequest(opt_payload) {
|
||||
return jwt.sign(data, secret, options);
|
||||
}
|
||||
exports.fillJwtForRequest = fillJwtForRequest;
|
||||
exports.forwarded = forwarded;
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
"cron": "^1.1.0",
|
||||
"express": "^4.14.0",
|
||||
"fakeredis": "^1.0.3",
|
||||
"forwarded": "^0.1.0",
|
||||
"jsonwebtoken": "^7.1.9",
|
||||
"jwa": "^1.1.4",
|
||||
"mime": "^1.3.4",
|
||||
|
||||
@ -283,7 +283,7 @@ function convertRequest(req, res) {
|
||||
var status = yield* convertByCmd(cmd, async, utils.getBaseUrlByRequest(req));
|
||||
utils.fillResponse(req, res, status.url, status.err);
|
||||
} else {
|
||||
var addresses = forwarded(req);
|
||||
var addresses = utils.forwarded(req);
|
||||
logger.error('Error convert unknown outputtype: query = %j from = %s docId = %s', params, addresses, docId);
|
||||
utils.fillResponse(req, res, undefined, constants.UNKNOWN);
|
||||
}
|
||||
|
||||
@ -30,39 +30,39 @@
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const cluster = require('cluster');
|
||||
const configCommon = require('config');
|
||||
const config = configCommon.get('services.CoAuthoring');
|
||||
//process.env.NODE_ENV = config.get('server.mode');
|
||||
|
||||
const logger = require('./../../Common/sources/logger');
|
||||
const co = require('co');
|
||||
|
||||
if (cluster.isMaster) {
|
||||
const fs = require('fs');
|
||||
const co = require('co');
|
||||
|
||||
//const numCPUs = require('os').cpus().length;
|
||||
const license = require('./../../Common/sources/license');
|
||||
|
||||
//const cfgWorkerPerCpu = config.get('server.workerpercpu');
|
||||
var licenseInfo, workersCount = 0;
|
||||
let licenseInfo, workersCount = 0, updateTime;
|
||||
const readLicense = function*() {
|
||||
licenseInfo = yield* license.readLicense();
|
||||
workersCount = Math.min(1, licenseInfo.count/*, Math.ceil(numCPUs * cfgWorkerPerCpu)*/);
|
||||
};
|
||||
const updateLicenseWorker = (worker) => {
|
||||
worker.send({data: licenseInfo});
|
||||
worker.send({type: 1, data: licenseInfo});
|
||||
};
|
||||
const updateWorkers = () => {
|
||||
var i;
|
||||
const arrKeyWorkers = Object.keys(cluster.workers);
|
||||
if (arrKeyWorkers.length < workersCount) {
|
||||
for (i = arrKeyWorkers.length; i < workersCount; ++i) {
|
||||
for (let i = arrKeyWorkers.length; i < workersCount; ++i) {
|
||||
const newWorker = cluster.fork();
|
||||
logger.warn('worker %s started.', newWorker.process.pid);
|
||||
}
|
||||
} else {
|
||||
for (i = workersCount; i < arrKeyWorkers.length; ++i) {
|
||||
for (let i = workersCount; i < arrKeyWorkers.length; ++i) {
|
||||
const killWorker = cluster.workers[arrKeyWorkers[i]];
|
||||
if (killWorker) {
|
||||
killWorker.kill();
|
||||
@ -70,12 +70,23 @@ if (cluster.isMaster) {
|
||||
}
|
||||
}
|
||||
};
|
||||
const updatePlugins = (eventType, filename) => {
|
||||
console.log('update Folder: %s ; %s', eventType, filename);
|
||||
if (updateTime && 1000 >= (new Date() - updateTime)) {
|
||||
return;
|
||||
}
|
||||
console.log('update Folder true: %s ; %s', eventType, filename);
|
||||
updateTime = new Date();
|
||||
for (let i in cluster.workers) {
|
||||
cluster.workers[i].send({type: 2});
|
||||
}
|
||||
};
|
||||
const updateLicense = () => {
|
||||
return co(function*() {
|
||||
try {
|
||||
yield* readLicense();
|
||||
logger.warn('update cluster with %s workers', workersCount);
|
||||
for (var i in cluster.workers) {
|
||||
for (let i in cluster.workers) {
|
||||
updateLicenseWorker(cluster.workers[i]);
|
||||
}
|
||||
updateWorkers();
|
||||
@ -95,16 +106,22 @@ if (cluster.isMaster) {
|
||||
|
||||
updateLicense();
|
||||
|
||||
try {
|
||||
fs.watch(config.get('plugins.path'), updatePlugins);
|
||||
} catch (e) {
|
||||
logger.warn('Plugins watch exception (https://nodejs.org/docs/latest/api/fs.html#fs_availability).');
|
||||
}
|
||||
fs.watchFile(configCommon.get('license').get('license_file'), updateLicense);
|
||||
setInterval(updateLicense, 86400000);
|
||||
} else {
|
||||
logger.warn('Express server starting...');
|
||||
|
||||
const express = require('express');
|
||||
const http = require('http');
|
||||
const urlModule = require('url');
|
||||
const path = require('path');
|
||||
const bodyParser = require("body-parser");
|
||||
const mime = require('mime');
|
||||
const forwarded = require('forwarded');
|
||||
const docsCoServer = require('./DocsCoServer');
|
||||
const canvasService = require('./canvasservice');
|
||||
const converterService = require('./converterservice');
|
||||
@ -113,43 +130,37 @@ if (cluster.isMaster) {
|
||||
const constants = require('./../../Common/sources/constants');
|
||||
const utils = require('./../../Common/sources/utils');
|
||||
const configStorage = configCommon.get('storage');
|
||||
var configIpFilter = configCommon.get('services.CoAuthoring.ipfilter');
|
||||
var cfgIpFilterEseForRequest = configIpFilter.get('useforrequest');
|
||||
const app = express();
|
||||
var server = null;
|
||||
const server = http.createServer(app);
|
||||
|
||||
logger.warn('Express server starting...');
|
||||
|
||||
server = http.createServer(app);
|
||||
let userPlugins = null, updatePlugins = true;
|
||||
|
||||
if (config.has('server.static_content')) {
|
||||
var staticContent = config.get('server.static_content');
|
||||
for (var i = 0; i < staticContent.length; ++i) {
|
||||
var staticContentElem = staticContent[i];
|
||||
const staticContent = config.get('server.static_content');
|
||||
for (let i = 0; i < staticContent.length; ++i) {
|
||||
const staticContentElem = staticContent[i];
|
||||
app.use(staticContentElem['name'], express.static(staticContentElem['path'], staticContentElem['options']));
|
||||
}
|
||||
}
|
||||
|
||||
if (configStorage.has('fs.folderPath')) {
|
||||
var cfgBucketName = configStorage.get('bucketName');
|
||||
var cfgStorageFolderName = configStorage.get('storageFolderName');
|
||||
const cfgBucketName = configStorage.get('bucketName');
|
||||
const cfgStorageFolderName = configStorage.get('storageFolderName');
|
||||
app.use('/' + cfgBucketName + '/' + cfgStorageFolderName, (req, res, next) => {
|
||||
var index = req.url.lastIndexOf('/');
|
||||
const index = req.url.lastIndexOf('/');
|
||||
if ('GET' === req.method && -1 != index) {
|
||||
var contentDisposition = req.query['disposition'] || 'attachment';
|
||||
var sendFileOptions = {
|
||||
root: configStorage.get('fs.folderPath'),
|
||||
dotfiles: 'deny',
|
||||
headers: {
|
||||
const contentDisposition = req.query['disposition'] || 'attachment';
|
||||
let sendFileOptions = {
|
||||
root: configStorage.get('fs.folderPath'), dotfiles: 'deny', headers: {
|
||||
'Content-Disposition': contentDisposition
|
||||
}
|
||||
};
|
||||
var urlParsed = urlModule.parse(req.url);
|
||||
const urlParsed = urlModule.parse(req.url);
|
||||
if (urlParsed && urlParsed.pathname) {
|
||||
var filename = decodeURIComponent(path.basename(urlParsed.pathname));
|
||||
const filename = decodeURIComponent(path.basename(urlParsed.pathname));
|
||||
sendFileOptions.headers['Content-Type'] = mime.lookup(filename);
|
||||
}
|
||||
var realUrl = req.url.substring(0, index);
|
||||
const realUrl = req.url.substring(0, index);
|
||||
res.sendFile(realUrl, sendFileOptions, (err) => {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
@ -161,19 +172,7 @@ if (cluster.isMaster) {
|
||||
}
|
||||
});
|
||||
}
|
||||
function checkClientIp(req, res, next) {
|
||||
var status = 0;
|
||||
if (cfgIpFilterEseForRequest) {
|
||||
var addresses = forwarded(req);
|
||||
var ipString = addresses[addresses.length - 1];
|
||||
status = utils.checkIpFilter(ipString);
|
||||
}
|
||||
if (status > 0) {
|
||||
res.sendStatus(status);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
// Если захочется использовать 'development' и 'production',
|
||||
// то с помощью app.settings.env (https://github.com/strongloop/express/issues/936)
|
||||
// Если нужна обработка ошибок, то теперь она такая https://github.com/expressjs/errorhandler
|
||||
@ -185,26 +184,28 @@ if (cluster.isMaster) {
|
||||
app.get('/index.html', (req, res) => {
|
||||
res.send('Server is functioning normally. Version: ' + docsCoServer.version);
|
||||
});
|
||||
var rawFileParser = bodyParser.raw({ inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*' });
|
||||
const rawFileParser = bodyParser.raw(
|
||||
{inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*'});
|
||||
|
||||
app.get('/coauthoring/CommandService.ashx', checkClientIp, rawFileParser, docsCoServer.commandFromServer);
|
||||
app.post('/coauthoring/CommandService.ashx', checkClientIp, rawFileParser, docsCoServer.commandFromServer);
|
||||
app.get('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer);
|
||||
app.post('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser,
|
||||
docsCoServer.commandFromServer);
|
||||
|
||||
if (config.has('server.fonts_route')) {
|
||||
var fontsRoute = config.get('server.fonts_route');
|
||||
const fontsRoute = config.get('server.fonts_route');
|
||||
app.get('/' + fontsRoute + 'native/:fontname', fontService.getFont);
|
||||
app.get('/' + fontsRoute + 'js/:fontname', fontService.getFont);
|
||||
app.get('/' + fontsRoute + 'odttf/:fontname', fontService.getFont);
|
||||
}
|
||||
|
||||
app.get('/ConvertService.ashx', checkClientIp, rawFileParser, converterService.convert);
|
||||
app.post('/ConvertService.ashx', checkClientIp, rawFileParser, converterService.convert);
|
||||
app.get('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convert);
|
||||
app.post('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convert);
|
||||
|
||||
|
||||
app.get('/FileUploader.ashx', checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
|
||||
app.post('/FileUploader.ashx', checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
|
||||
app.get('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
|
||||
app.post('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
|
||||
|
||||
var docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
|
||||
const docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
|
||||
app.param('docid', (req, res, next, val) => {
|
||||
if (docIdRegExp.test(val)) {
|
||||
next();
|
||||
@ -223,14 +224,53 @@ if (cluster.isMaster) {
|
||||
app.post('/upload/:docid/:userid/:index/:jwt?', rawFileParser, fileUploaderService.uploadImageFile);
|
||||
|
||||
app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs);
|
||||
app.get('/healthcheck', checkClientIp, converterService.convertHealthCheck);
|
||||
app.get('/healthcheck', utils.checkClientIp, converterService.convertHealthCheck);
|
||||
|
||||
const sendUserPlugins = (res, data) => {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(JSON.stringify(data));
|
||||
};
|
||||
app.get('/sdkjs-plugins/config.json', utils.checkClientIp, (req, res) => {
|
||||
if (userPlugins && !updatePlugins) {
|
||||
sendUserPlugins(res, userPlugins);
|
||||
return;
|
||||
}
|
||||
let pluginsPath = config.get('plugins.path');
|
||||
utils.listFolders(pluginsPath, true).then((values) => {
|
||||
return co(function*() {
|
||||
let stats = null;
|
||||
let result = [];
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
try {
|
||||
stats = yield utils.fsStat(path.join(values[i], 'config.json'));
|
||||
} catch (err) {
|
||||
stats = null;
|
||||
}
|
||||
|
||||
if (stats && stats.isFile) {
|
||||
result.push(path.basename(values[i]) + '/config.json');
|
||||
}
|
||||
}
|
||||
|
||||
userPlugins = {'url': config.get('plugins.url'), 'pluginsData': result};
|
||||
sendUserPlugins(res, userPlugins);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
process.on('message', (msg) => {
|
||||
if (!docsCoServer) {
|
||||
return;
|
||||
}
|
||||
switch (msg.type) {
|
||||
case 1:
|
||||
docsCoServer.setLicenseInfo(msg.data);
|
||||
break;
|
||||
case 2:
|
||||
updatePlugins = true;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user