Merge branch 'release/v4.1.0'

This commit is contained in:
Alexey Golubev
2016-09-05 13:47:29 +03:00
30 changed files with 680 additions and 383 deletions

View File

@ -52,7 +52,8 @@
"limits_image_download_timeout": 120,
"editor_settings_spellchecker_url": "/spellchecker",
"callbackRequestTimeout": 120,
"healthcheckfilepath": "../public/healthcheck.docx"
"healthcheckfilepath": "../public/healthcheck.docx",
"savetimeoutdelay": 5000
},
"utils": {
"utils_common_fontdir": "null",
@ -62,16 +63,17 @@
"limits_image_types_copy": "jpg;png;gif;bmp;emf;wmf;svg;txt;bin"
},
"sql": {
"type": "mysql",
"type": "postgres",
"tableChanges": "doc_changes",
"tableCallbacks": "doc_callbacks",
"tableResult": "task_result",
"dbHost": "localhost",
"dbPort": 3306,
"dbPort": 5432,
"dbName": "onlyoffice",
"dbUser": "root",
"dbUser": "onlyoffice",
"dbPass": "onlyoffice",
"charset": "utf8",
"connectionlimit": 10,
"max_allowed_packet": 1048575
},
"redis": {
@ -98,6 +100,11 @@
"files": 604800,
"filesCron": "00 00 */1 * * *",
"filesremovedatonce": 10
},
"ipfilter": {
"rules": [{"address": "*", "allowed": true}],
"useforrequest": false,
"errorcode": 401
}
}
},

View File

@ -32,6 +32,12 @@
},
"utils": {
"utils_common_fontdir": "/Library/Fonts"
},
"sql": {
"type": "mysql",
"dbPort": 3306,
"dbUser": "root",
"dbPass": "onlyoffice"
}
}
},

View File

@ -32,6 +32,12 @@
},
"utils": {
"utils_common_fontdir": "C:\\Windows\\Fonts"
},
"sql": {
"type": "mysql",
"dbPort": 3306,
"dbUser": "root",
"dbPass": "onlyoffice"
}
}
},

View File

@ -1,22 +0,0 @@
{
"services": {
"CoAuthoring": {
"utils": {
"utils_common_fontdir": "C:\\Windows\\Fonts",
}
}
},
"FileConverter": {
"converter": {
"fontDir": "C:\\Windows\\Fonts",
"presentationThemesDir": "../../../OfficeWeb/sdk/PowerPoint/themes",
"filePath": "../Bin/x2t.exe"
}
},
"license": {
"license_file": "./../../license.lic"
},
"FileStorage": {
"directory": "../App_Data"
}
}

View File

@ -5,14 +5,16 @@
"private": true,
"dependencies": {
"amazon-s3-url-signer": "https://github.com/agolybev/amazon-s3-url-signer/archive/v0.0.9.tar.gz",
"amqplib": "^0.4.0",
"aws-sdk": "^2.2.33",
"amqplib": "^0.4.1",
"aws-sdk": "^2.4.12",
"co": "^4.6.0",
"config": "^1.19.0",
"log4js": "^0.6.31",
"config": "^1.21.0",
"escape-string-regexp": "^1.0.5",
"log4js": "^0.6.38",
"mime": "^1.3.4",
"mkdirp": "^0.5.1",
"node-statsd": "^0.1.1",
"request": "^2.69.0"
"request": "^2.74.0",
"uri-js": "^2.1.1"
}
}

View File

@ -285,6 +285,7 @@ function CMailMergeSendData(obj) {
this['recordFrom'] = obj['recordFrom'];
this['recordTo'] = obj['recordTo'];
this['recordCount'] = obj['recordCount'];
this['recordErrorCount'] = obj['recordErrorCount'];
this['userId'] = obj['userId'];
this['url'] = obj['url'];
this['baseUrl'] = obj['baseUrl'];
@ -299,6 +300,7 @@ function CMailMergeSendData(obj) {
this['recordFrom'] = null;
this['recordTo'] = null;
this['recordCount'] = null;
this['recordErrorCount'] = null;
this['userId'] = null;
this['url'] = null;
this['baseUrl'] = null;
@ -359,6 +361,12 @@ CMailMergeSendData.prototype.getRecordCount = function() {
CMailMergeSendData.prototype.setRecordCount = function(v) {
this['recordCount'] = v;
};
CMailMergeSendData.prototype.getRecordErrorCount = function() {
return this['recordErrorCount']
};
CMailMergeSendData.prototype.setRecordErrorCount = function(v) {
this['recordErrorCount'] = v;
};
CMailMergeSendData.prototype.getUserId = function() {
return this['userId']
};
@ -463,8 +471,8 @@ function OutputSfcData() {
this['url'] = undefined;
this['changesurl'] = undefined;
this['changeshistory'] = undefined;
this['users'] = [];
this['actions'] = [];
this['users'] = undefined;
this['actions'] = undefined;
this['mailMerge'] = undefined;
this['userdata'] = undefined;
}
@ -544,6 +552,7 @@ function OutputMailMerge(mailMergeSendData) {
break;
}
this['recordCount'] = mailMergeSendData.getRecordCount();
this['recordErrorCount'] = mailMergeSendData.getRecordErrorCount();
this['to'] = null;
this['recordIndex'] = null;
} else {
@ -555,6 +564,7 @@ function OutputMailMerge(mailMergeSendData) {
this['type'] = null;
this['recordCount'] = null;
this['recordIndex'] = null;
this['recordErrorCount'] = null;
}
}
OutputMailMerge.prototype.getRecordIndex = function() {
@ -563,6 +573,12 @@ OutputMailMerge.prototype.getRecordIndex = function() {
OutputMailMerge.prototype.setRecordIndex = function(data) {
return this['recordIndex'] = data;
};
OutputMailMerge.prototype.getRecordErrorCount = function() {
return this['recordErrorCount'];
};
OutputMailMerge.prototype.setRecordErrorCount = function(data) {
return this['recordErrorCount'] = data;
};
OutputMailMerge.prototype.getTo = function() {
return this['to'];
};
@ -670,8 +686,7 @@ var c_oAscEncodingsMap = {"437": 43, "720": 1, "737": 21, "775": 5, "850": 39, "
var c_oAscCodePageUtf8 = 46;//65001
var c_oAscUserAction = {
Out: 0,
In: 1,
AllIn: 2
In: 1
};
var c_oAscServerCommandErrors = {
NoError: 0,

View File

@ -41,7 +41,8 @@ exports.LICENSE_RESULT = {
Expired : 2,
Success : 3,
UnknownUser : 4,
Connections : 5
Connections : 5,
ExpiredTrial: 6
};
exports.LICENSE_CONNECTIONS = 21;
@ -127,7 +128,6 @@ exports.CONVERT_DOWNLOAD = -81;
exports.CONVERT_UNKNOWN_FORMAT = -82;
exports.CONVERT_TIMEOUT = -83;
exports.CONVERT_READ_FILE = -84;
exports.CONVERT_MS_OFFCRYPTO = -85;
exports.CONVERT_CORRUPTED = -86;
exports.CONVERT_LIBREOFFICE = -87;
exports.CONVERT_PARAMS = -88;
@ -156,6 +156,9 @@ exports.EDITOR_TYPE_SPREADSHEET = 1;
exports.EDITOR_TYPE_PRESENTATION = 2;
exports.EDITOR_TYPE_CONVERTATION = 3;
exports.PACKAGE_TYPE_OS = 0;
exports.PACKAGE_TYPE_I = 1;
exports.REDIS_KEY_PUBSUB = 'pubsub';
exports.REDIS_KEY_SAVE_LOCK = 'savelock:';
exports.REDIS_KEY_PRESENCE_HASH = 'presence:hash:';
@ -170,6 +173,7 @@ exports.REDIS_KEY_FORCE_SAVE = 'forcesave:';
exports.REDIS_KEY_SAVED = 'saved:';
exports.REDIS_KEY_SHUTDOWN = 'shutdown';
exports.REDIS_KEY_LICENSE = 'license';
exports.REDIS_KEY_LICENSE_T = 'licenseT';
exports.SHUTDOWN_CODE = 4001;
exports.SHUTDOWN_REASON = 'server shutdown';

View File

@ -40,17 +40,20 @@ const utils = require('./utils');
const pubsubRedis = require('./../../DocService/sources/pubsubRedis');
const redisClient = pubsubRedis.getClientRedis();
const cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix');
const redisKeyLicense = cfgRedisPrefix + constants.REDIS_KEY_LICENSE;
const buildVersion = '4.0.0';
const buildNumber = 19;
const buildDate = '6/29/2016';
const oBuildDate = new Date(buildDate);
const oPackageType = constants.PACKAGE_TYPE_OS;
const cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix');
const redisKeyLicense = cfgRedisPrefix + ((constants.PACKAGE_TYPE_OS === oPackageType) ? constants.REDIS_KEY_LICENSE :
constants.REDIS_KEY_LICENSE_T);
exports.readLicense = function*() {
const resMax = {count: 999999, type: constants.LICENSE_RESULT.Success};
var res = {count: 1, type: constants.LICENSE_RESULT.Error, light: false};
const c_LR = constants.LICENSE_RESULT;
const resMax = {count: 999999, type: c_LR.Success};
var res = {count: 1, type: c_LR.Error, light: false, packageType: oPackageType};
var checkFile = false;
try {
var oFile = fs.readFileSync(configL.get('license_file')).toString();
@ -64,39 +67,64 @@ exports.readLicense = function*() {
const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRhGF7X4A0ZVlEg594WmODVVUI\niiPQs04aLmvfg8SborHss5gQXu0aIdUT6nb5rTh5hD2yfpF2WIW6M8z0WxRhwicg\nXwi80H1aLPf6lEPPLvN29EhQNjBpkFkAJUbS8uuhJEeKw0cE49g80eBBF4BCqSL6\nPFQbP9/rByxdxEoAIQIDAQAB\n-----END PUBLIC KEY-----\n';
if (verify.verify(publicKey, sign, 'hex')) {
const endDate = new Date(oLicense['end_date']);
const checkDate = (true === oLicense['trial'] || 'true' === oLicense['trial']) ? new Date() : oBuildDate; // Someone who likes to put json string instead of bool
const isTrial = (true === oLicense['trial'] || 'true' === oLicense['trial']);
const checkDate = isTrial ? new Date() : oBuildDate; // Someone who likes to put json string instead of bool
if (endDate >= checkDate && 2 <= oLicense['version']) {
res.count = Math.min(Math.max(res.count, oLicense['process'] >> 0), resMax.count);
res.type = constants.LICENSE_RESULT.Success;
res.type = c_LR.Success;
} else {
res.type = constants.LICENSE_RESULT.Expired;
res.type = isTrial ? c_LR.ExpiredTrial : c_LR.Expired;
}
res.light = (true === oLicense['light'] || 'true' === oLicense['light']); // Someone who likes to put json string instead of bool
}
} catch (e) {
res.count = 1;
res.type = constants.LICENSE_RESULT.Error;
res.type = c_LR.Error;
if (checkFile || (yield* _getFileState())) {
res.type = constants.LICENSE_RESULT.Expired;
if (checkFile) {
res.type = c_LR.ExpiredTrial;
} else {
if (constants.PACKAGE_TYPE_OS === oPackageType) {
if (yield* _getFileState()) {
res.type = c_LR.ExpiredTrial;
}
} else {
res.type = (yield* _getFileState()) ? c_LR.Success : c_LR.ExpiredTrial;
if (res.type === c_LR.Success) {
return res;
}
}
}
}
if (res.type === constants.LICENSE_RESULT.Expired) {
res.count = 0;
if (res.type === c_LR.Expired || res.type === c_LR.ExpiredTrial) {
res.count = 1;
logger.error('License Expired!!!');
}
if (checkFile) {
yield* _updateFileState();
yield* _updateFileState(true);
}
return res;
};
function* _getFileState() {
return yield utils.promiseRedis(redisClient, redisClient.hget, redisKeyLicense, redisKeyLicense);
const val = yield utils.promiseRedis(redisClient, redisClient.hget, redisKeyLicense, redisKeyLicense);
if (constants.PACKAGE_TYPE_OS === oPackageType) {
return val;
}
if (null === val) {
yield* _updateFileState(false);
return true;
}
var now = new Date();
now.setMonth(now.getMonth() - 1);
return (0 >= (now - new Date(val)));
}
function* _updateFileState() {
yield utils.promiseRedis(redisClient, redisClient.hset, redisKeyLicense, redisKeyLicense, redisKeyLicense);
function* _updateFileState(state) {
const val = constants.PACKAGE_TYPE_OS === oPackageType ? redisKeyLicense : (state ? new Date(1) : new Date());
yield utils.promiseRedis(redisClient, redisClient.hset, redisKeyLicense, redisKeyLicense, val);
}

View File

@ -72,7 +72,7 @@ function connetPromise(closeCallback) {
var closeEventCallback = function() {
//in some case receive multiple close events
conn.removeListener('close', closeEventCallback);
console.debug("[AMQP] conn close");
logger.debug('[AMQP] conn close');
closeCallback();
};
conn.on('close', closeEventCallback);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -119,24 +119,14 @@ function clear(taskqueue) {
taskqueue.channelConvertResponseReceive = null;
}
function repeat(taskqueue) {
var i;
for (i = 0; i < taskqueue.addTaskStore.length; ++i) {
//repeat addTask because they are lost after the reconnection
//unlike unconfirmed task will come again
//acknowledge data after reconnect raises an exception 'PRECONDITION_FAILED - unknown delivery tag'
for (var i = 0; i < taskqueue.addTaskStore.length; ++i) {
var elem = taskqueue.addTaskStore[i];
addTask(taskqueue, elem.task, elem.priority, function () {});
}
for (i = 0; i < taskqueue.addResponseStore.length; ++i) {
addResponse(taskqueue, taskqueue.addResponseStore[i], function () {});
}
for (i = 0; i < taskqueue.removeTaskStore.length; ++i) {
removeTask(taskqueue, taskqueue.removeTaskStore[i]);
}
for (i = 0; i < taskqueue.removeResponseStore.length; ++i) {
removeResponse(taskqueue, taskqueue.removeResponseStore[i]);
}
taskqueue.addTaskStore.length = 0;
taskqueue.addResponseStore.length = 0;
taskqueue.removeTaskStore.length = 0;
taskqueue.removeResponseStore.length = 0;
}
function addTask(taskqueue, content, priority, callback) {
var options = {persistent: true, priority: priority};
@ -161,9 +151,6 @@ function TaskQueueRabbitMQ() {
this.channelConvertResponse = null;
this.channelConvertResponseReceive = null;
this.addTaskStore = [];
this.addResponseStore = [];
this.removeTaskStore = [];
this.removeResponseStore = [];
}
util.inherits(TaskQueueRabbitMQ, events.EventEmitter);
TaskQueueRabbitMQ.prototype.init = function (isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, callback) {
@ -214,7 +201,6 @@ TaskQueueRabbitMQ.prototype.addResponse = function (task) {
}
});
} else {
t.addResponseStore.push(content);
resolve();
}
});
@ -224,8 +210,6 @@ TaskQueueRabbitMQ.prototype.removeTask = function (data) {
return new Promise(function (resolve, reject) {
if (null != t.channelConvertTaskReceive) {
removeTask(t, data);
} else {
t.removeTaskStore.push(data);
}
resolve();
});
@ -235,8 +219,6 @@ TaskQueueRabbitMQ.prototype.removeResponse = function (data) {
return new Promise(function (resolve, reject) {
if (null != t.channelConvertResponseReceive) {
removeResponse(t, data);
} else {
t.removeResponseStore.push(data);
}
resolve();
});

View File

@ -29,16 +29,33 @@
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
var config = require('config');
var fs = require('fs');
var path = require('path');
var url = require('url');
var request = require('request');
var co = require('co');
var URI = require("uri-js");
const escapeStringRegexp = require('escape-string-regexp');
var constants = require('./constants');
var configIpFilter = config.get('services.CoAuthoring.ipfilter');
var cfgIpFilterRules = configIpFilter.get('rules');
var cfgIpFilterErrorCode = configIpFilter.get('errorcode');
var ANDROID_SAFE_FILENAME = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-+,@£$€!½§~\'=()[]{}0123456789';
var g_oIpFilterRules = function() {
var res = [];
for (var i = 0; i < cfgIpFilterRules.length; ++i) {
var rule = cfgIpFilterRules[i];
var regExpStr = rule['address'].split('*').map(escapeStringRegexp).join('.*');
var exp = new RegExp('^' + regExpStr + '$', 'i');
res.push({allow: rule['allowed'], exp: exp});
}
return res;
}();
exports.addSeconds = function(date, sec) {
date.setSeconds(date.getSeconds() + sec);
};
@ -192,19 +209,16 @@ exports.getContentDisposition = getContentDisposition;
exports.getContentDispositionS3 = getContentDispositionS3;
function downloadUrlPromise(uri, optTimeout, optLimit) {
return new Promise(function (resolve, reject) {
//todo может стоит делать url.parse, а потом с каждой частью отдельно работать
//для ссылок с руссикими буквами приходит 404
if (!containsAllAsciiNP(uri)) {
uri = encodeURI(uri);
}
//IRI to URI
uri = URI.serialize(URI.parse(uri));
var urlParsed = url.parse(uri);
//if you expect binary data, you should set encoding: null
var options = {uri: urlParsed, encoding: null, timeout: optTimeout};
if (urlParsed.protocol === 'https:') {
//TODO: Check how to correct handle a ssl link
urlParsed.rejectUnauthorized = false;
options.rejectUnauthorized = false;
}
//TODO: Check how to correct handle a ssl link
urlParsed.rejectUnauthorized = false;
options.rejectUnauthorized = false;
request.get(options, function (err, response, body) {
if (err) {
reject(err);
@ -227,18 +241,15 @@ function downloadUrlPromise(uri, optTimeout, optLimit) {
}
function postRequestPromise(uri, postData, optTimeout) {
return new Promise(function(resolve, reject) {
//todo может стоит делать url.parse, а потом с каждой частью отдельно работать
//для ссылок с руссикими буквами приходит 404
if (!containsAllAsciiNP(uri)) {
uri = encodeURI(uri);
}
//IRI to URI
uri = URI.serialize(URI.parse(uri));
var urlParsed = url.parse(uri);
var options = {uri: urlParsed, body: postData, encoding: 'utf8', headers: {'Content-Type': 'application/json'}, timeout: optTimeout};
if (urlParsed.protocol === 'https:') {
//TODO: Check how to correct handle a ssl link
urlParsed.rejectUnauthorized = false;
options.rejectUnauthorized = false;
}
//TODO: Check how to correct handle a ssl link
urlParsed.rejectUnauthorized = false;
options.rejectUnauthorized = false;
request.post(options, function(err, response, body) {
if (err) {
reject(err);
@ -337,7 +348,7 @@ exports.fillXmlResponse = function(res, uri, error) {
res.setHeader('Content-Length', body.length);
res.send(body);
};
exports.promiseCreateWriteStream = function(strPath, optOptions) {
function promiseCreateWriteStream(strPath, optOptions) {
return new Promise(function(resolve, reject) {
var file = fs.createWriteStream(strPath, optOptions);
var errorCallback = function(e) {
@ -350,7 +361,8 @@ exports.promiseCreateWriteStream = function(strPath, optOptions) {
});
});
};
exports.promiseCreateReadStream = function(strPath) {
exports.promiseCreateWriteStream = promiseCreateWriteStream;
function promiseCreateReadStream(strPath) {
return new Promise(function(resolve, reject) {
var file = fs.createReadStream(strPath);
var errorCallback = function(e) {
@ -363,6 +375,7 @@ exports.promiseCreateReadStream = function(strPath) {
});
});
};
exports.promiseCreateReadStream = promiseCreateReadStream;
exports.compareStringByLength = function(x, y) {
if (x && y) {
if (x.length == y.length) {
@ -480,3 +493,35 @@ function changeOnlyOfficeUrl(inputUrl, strPath, optFilename) {
return inputUrl + constants.ONLY_OFFICE_URL_PARAM + '=' + constants.OUTPUT_NAME + path.extname(optFilename || strPath);
}
exports.changeOnlyOfficeUrl = changeOnlyOfficeUrl;
function pipeStreams(from, to, isEnd) {
return new Promise(function(resolve, reject) {
from.pipe(to, {end: isEnd});
from.on('end', function() {
resolve();
});
from.on('error', function(e) {
reject(e);
});
});
}
exports.pipeStreams = pipeStreams;
function* pipeFiles(from, to) {
var fromStream = yield promiseCreateReadStream(from);
var toStream = yield promiseCreateWriteStream(to);
yield pipeStreams(fromStream, toStream, true);
}
exports.pipeFiles = co.wrap(pipeFiles);
function checkIpFilter(hostname) {
var status = 0;
for (var i = 0; i < g_oIpFilterRules.length; ++i) {
var rule = g_oIpFilterRules[i];
if (rule.exp.test(hostname)) {
if (!rule.allow) {
status = cfgIpFilterErrorCode;
}
break;
}
}
return status;
}
exports.checkIpFilter = checkIpFilter;

View File

@ -5,17 +5,20 @@
"private": true,
"dependencies": {
"base64-stream": "^0.1.3",
"body-parser": "^1.14.2",
"body-parser": "^1.15.2",
"co": "^4.6.0",
"config": "^1.19.0",
"config": "^1.21.0",
"cron": "^1.1.0",
"express": "^4.13.4",
"fakeredis": "^1.0.2",
"express": "^4.14.0",
"fakeredis": "^1.0.3",
"forwarded": "^0.1.0",
"ipaddr.js": "^1.2.0",
"mime": "^1.3.4",
"multiparty": "^4.1.2",
"mysql": "^2.10.2",
"pg": "^4.4.4",
"redis": "^2.4.2",
"mysql": "^2.11.1",
"pg": "^6.1.0",
"pg-escape": "^0.2.0",
"redis": "^2.6.2",
"sockjs": "http://d2ettrnqo7v976.cloudfront.net/npm/sockjs-node/v0.3.15.14.tar.gz",
"underscore": "^1.8.3"
}

View File

@ -49,7 +49,7 @@
* d) Когда пользователь остается один, после принятия чужих изменений начинается пункт 'а'
*-----------------------------------------------------------------------------------------------------------------------
*--------------------------------------------Схема работы с сервером----------------------------------------------------
* а) Когда все уходят, спустя время c_oAscSaveTimeOutDelay на сервер документов шлется команда на сборку.
* а) Когда все уходят, спустя время cfgAscSaveTimeOutDelay на сервер документов шлется команда на сборку.
* b) Если приходит статус '1' на CommandService.ashx, то удалось сохранить и поднять версию. Очищаем callback-и и
* изменения из базы и из памяти.
* с) Если приходит статус, отличный от '1'(сюда можно отнести как генерацию файла, так и работа внешнего подписчика
@ -95,6 +95,8 @@ var pubsubService = require('./' + config.get('pubsub.name'));
var queueService = require('./../../Common/sources/taskqueueRabbitMQ');
var cfgSpellcheckerUrl = config.get('server.editor_settings_spellchecker_url');
var cfgCallbackRequestTimeout = config.get('server.callbackRequestTimeout');
//The waiting time to document assembly when all out(not 0 in case of F5 in the browser)
var cfgAscSaveTimeOutDelay = config.get('server.savetimeoutdelay');
var cfgPubSubMaxChanges = config.get('pubsub.maxChanges');
@ -183,7 +185,6 @@ var c_oAscChangeBase = {
All: 2
};
var c_oAscSaveTimeOutDelay = 5000; // Время ожидания для сохранения на сервере (для отработки F5 в браузере)
var c_oAscLockTimeOutDelay = 500; // Время ожидания для сохранения, когда зажата база данных
var c_oAscRecalcIndexTypes = {
@ -531,7 +532,10 @@ function* sendServerRequest(docId, uri, postData) {
function parseUrl(callbackUrl) {
var result = null;
try {
var parseObject = url.parse(decodeURIComponent(callbackUrl));
//делать decodeURIComponent не нужно http://expressjs.com/en/4x/api.html#app.settings.table
//по умолчанию express использует 'query parser' = 'extended', но даже в 'simple' версии делается decode
//percent-encoded characters within the query string will be assumed to use UTF-8 encoding
var parseObject = url.parse(callbackUrl);
var isHttps = 'https:' === parseObject.protocol;
var port = parseObject.port;
if (!port) {
@ -601,11 +605,14 @@ function* setForceSave(docId, lastSave, savePathDoc) {
* @param callback
* @param baseUrl
*/
function* sendStatusDocument(docId, bChangeBase, userAction, callback, baseUrl) {
function* sendStatusDocument(docId, bChangeBase, userAction, callback, baseUrl, opt_userData) {
if (!callback) {
var getRes = yield* getCallback(docId);
if(getRes) {
if (getRes) {
callback = getRes.server;
if (!baseUrl) {
baseUrl = getRes.baseUrl;
}
}
}
if (null == callback) {
@ -634,21 +641,14 @@ function* sendStatusDocument(docId, bChangeBase, userAction, callback, baseUrl)
var sendData = new commonDefines.OutputSfcData();
sendData.setKey(docId);
sendData.setStatus(status);
if(c_oAscServerStatus.Closed !== status){
if (c_oAscServerStatus.Closed !== status) {
sendData.setUsers(participants);
} else {
sendData.setUsers(undefined);
}
if (userAction) {
var actions = [];
if (commonDefines.c_oAscUserAction.AllIn === userAction.type) {
for (var i = 0; i < participants.length; ++i) {
actions.push(new commonDefines.OutputAction(commonDefines.c_oAscUserAction.In, participants[i]));
}
} else {
actions.push(userAction);
}
sendData.setActions(actions);
sendData.setActions([userAction]);
}
if (opt_userData) {
sendData.setUserData(opt_userData);
}
var uri = callback.href;
var replyData = null;
@ -705,26 +705,34 @@ function dropUserFromDocument(docId, userId, description) {
}
// Подписка на эвенты:
function* bindEvents(docId, callback, baseUrl, opt_userAction) {
function* bindEvents(docId, callback, baseUrl, opt_userAction, opt_userData) {
// Подписка на эвенты:
// - если пользователей нет и изменений нет, то отсылаем статус "закрыто" и в базу не добавляем
// - если пользователей нет, а изменения есть, то отсылаем статус "редактируем" без пользователей, но добавляем в базу
// - если есть пользователи, то просто добавляем в базу
var bChangeBase = c_oAscChangeBase.Delete;
var getRes = yield* getCallback(docId);
var bChangeBase;
var oCallbackUrl;
var getRes = yield* getCallback(docId);
if (getRes) {
oCallbackUrl = getRes.server;
bChangeBase = c_oAscChangeBase.Delete;
} else {
oCallbackUrl = parseUrl(callback);
if (null === oCallbackUrl) {
return commonDefines.c_oAscServerCommandErrors.ParseError;
}
bChangeBase = c_oAscChangeBase.All;
if (null !== oCallbackUrl) {
if (utils.checkIpFilter(oCallbackUrl.host) > 0) {
logger.error('checkIpFilter error: docId = %s;url = %s', docId, callback);
//todo add new error type
oCallbackUrl = null;
}
}
}
if (null === oCallbackUrl) {
return commonDefines.c_oAscServerCommandErrors.ParseError;
} else {
yield* sendStatusDocument(docId, bChangeBase, opt_userAction, oCallbackUrl, baseUrl, opt_userData);
return commonDefines.c_oAscServerCommandErrors.NoError;
}
var userAction = opt_userAction ? opt_userAction : new commonDefines.OutputAction(commonDefines.c_oAscUserAction.AllIn, null);
yield* sendStatusDocument(docId, bChangeBase, userAction, oCallbackUrl, baseUrl);
return commonDefines.c_oAscServerCommandErrors.NoError;
}
function* cleanDocumentOnExit(docId, deleteChanges) {
@ -742,7 +750,7 @@ function* cleanDocumentOnExit(docId, deleteChanges) {
function* cleanDocumentOnExitNoChanges(docId, opt_userId) {
var userAction = opt_userId ? new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, opt_userId) : null;
// Отправляем, что все ушли и нет изменений (чтобы выставить статус на сервере об окончании редактирования)
yield* sendStatusDocument(docId, c_oAscChangeBase.All, userAction);
yield* sendStatusDocument(docId, c_oAscChangeBase.No, userAction);
//если пользователь зашел в документ, соединение порвалось, на сервере удалилась вся информация,
//при восстановлении соединения userIndex сохранится и он совпадет с userIndex следующего пользователя
yield* cleanDocumentOnExit(docId, false);
@ -758,7 +766,7 @@ function* _createSaveTimer(docId, opt_userId, opt_queue, opt_noDelay) {
var updateIfRes = yield taskResult.updateIf(updateTask, updateMask);
if (updateIfRes.affectedRows > 0) {
if(!opt_noDelay){
yield utils.sleep(c_oAscSaveTimeOutDelay);
yield utils.sleep(cfgAscSaveTimeOutDelay);
}
while (true) {
if (!sqlBase.isLockCriticalSection(docId)) {
@ -825,6 +833,10 @@ exports.install = function(server, callbackFunction) {
logger.debug('Server shutdown receive data');
return;
}
if (conn.isCiriticalError && ('message' == data.type || 'getLock' == data.type || 'saveChanges' == data.type)) {
logger.warn("conn.isCiriticalError send command: docId = %s type = %s", docId, data.type);
return;
}
switch (data.type) {
case 'auth' :
yield* auth(conn, data);
@ -1085,6 +1097,7 @@ exports.install = function(server, callbackFunction) {
function sendFileError(conn, errorId) {
logger.error('error description: docId = %s errorId = %s', conn.docId, errorId);
conn.isCiriticalError = true;
sendData(conn, {type: 'error', description: errorId});
}
@ -1235,6 +1248,16 @@ exports.install = function(server, callbackFunction) {
return resultLock;
}
function* authRestore(conn, sessionId) {
conn.sessionId = sessionId;//restore old
//Kill previous connections
connections = _.reject(connections, function(el) {
return el.sessionId === sessionId;//Delete this connection
});
yield* endAuth(conn, true);
}
function* auth(conn, data) {
// Проверка версий
if (data.version !== asc_coAuthV) {
@ -1305,76 +1328,73 @@ exports.install = function(server, callbackFunction) {
if (bIsRestore) {
logger.info("restored old session: docId = %s id = %s", docId, data.sessionId);
// Останавливаем сборку (вдруг она началась)
// Когда переподсоединение, нам нужна проверка на сборку файла
try {
var result = yield sqlBase.checkStatusFilePromise(docId);
if (!conn.user.view) {
// Останавливаем сборку (вдруг она началась)
// Когда переподсоединение, нам нужна проверка на сборку файла
try {
var result = yield sqlBase.checkStatusFilePromise(docId);
var status = result && result.length > 0 ? result[0]['status'] : null;
if (taskResult.FileStatus.Ok === status) {
// Все хорошо, статус обновлять не нужно
} else if (taskResult.FileStatus.SaveVersion === status) {
// Обновим статус файла (идет сборка, нужно ее остановить)
var updateMask = new taskResult.TaskResultData();
updateMask.key = docId;
updateMask.status = status;
updateMask.statusInfo = result[0]['status_info'];
var updateTask = new taskResult.TaskResultData();
updateTask.status = taskResult.FileStatus.Ok;
updateTask.statusInfo = constants.NO_ERROR;
var updateIfRes = yield taskResult.updateIf(updateTask, updateMask);
if (!(updateIfRes.affectedRows > 0)) {
var status = result && result.length > 0 ? result[0]['status'] : null;
if (taskResult.FileStatus.Ok === status) {
// Все хорошо, статус обновлять не нужно
} else if (taskResult.FileStatus.SaveVersion === status) {
// Обновим статус файла (идет сборка, нужно ее остановить)
var updateMask = new taskResult.TaskResultData();
updateMask.key = docId;
updateMask.status = status;
updateMask.statusInfo = result[0]['status_info'];
var updateTask = new taskResult.TaskResultData();
updateTask.status = taskResult.FileStatus.Ok;
updateTask.statusInfo = constants.NO_ERROR;
var updateIfRes = yield taskResult.updateIf(updateTask, updateMask);
if (!(updateIfRes.affectedRows > 0)) {
// error version
sendFileError(conn, 'Update Version error');
return;
}
} else if (taskResult.FileStatus.UpdateVersion === status) {
// error version
sendFileError(conn, 'Update Version error');
return;
} else {
// Other error
sendFileError(conn, 'Other error');
return;
}
} else if (taskResult.FileStatus.UpdateVersion === status) {
// error version
sendFileError(conn, 'Update Version error');
return;
} else {
// Other error
sendFileError(conn, 'Other error');
return;
}
var objChangesDocument = yield* getDocumentChanges(docId);
var bIsSuccessRestore = true;
if (objChangesDocument && 0 < objChangesDocument.arrChanges.length) {
var change = objChangesDocument.arrChanges[objChangesDocument.getLength() - 1];
if (change['change']) {
if (change['user'] !== curUserId) {
bIsSuccessRestore = 0 === (((data['lastOtherSaveTime'] - change['time']) / 1000) >> 0);
var objChangesDocument = yield* getDocumentChanges(docId);
var bIsSuccessRestore = true;
if (objChangesDocument && 0 < objChangesDocument.arrChanges.length) {
var change = objChangesDocument.arrChanges[objChangesDocument.getLength() - 1];
if (change['change']) {
if (change['user'] !== curUserId) {
bIsSuccessRestore = 0 === (((data['lastOtherSaveTime'] - change['time']) / 1000) >> 0);
}
}
}
}
if (bIsSuccessRestore) {
conn.sessionId = data.sessionId;//restore old
// Проверяем lock-и
var arrayBlocks = data['block'];
var getLockRes = yield* getLock(conn, data, true);
if (arrayBlocks && (0 === arrayBlocks.length || getLockRes)) {
//Kill previous connections
connections = _.reject(connections, function(el) {
return el.sessionId === data.sessionId;//Delete this connection
});
yield* endAuth(conn, true);
if (bIsSuccessRestore) {
// Проверяем lock-и
var arrayBlocks = data['block'];
var getLockRes = yield* getLock(conn, data, true);
if (arrayBlocks && (0 === arrayBlocks.length || getLockRes)) {
yield* authRestore(conn, data.sessionId);
} else {
sendFileError(conn, 'Restore error. Locks not checked.');
}
} else {
sendFileError(conn, 'Restore error. Locks not checked.');
sendFileError(conn, 'Restore error. Document modified.');
}
} else {
sendFileError(conn, 'Restore error. Document modified.');
} catch (err) {
sendFileError(conn, 'DataBase error\r\n' + err.stack);
}
} catch (err) {
sendFileError(conn, 'DataBase error\r\n' + err.stack);
} else {
yield* authRestore(conn, data.sessionId);
}
} else {
conn.sessionId = conn.id;
yield* endAuth(conn, false, data.documentCallbackUrl);
if (cmd) {
var endAuthRes = yield* endAuth(conn, false, data.documentCallbackUrl);
if (endAuthRes && cmd) {
yield canvasService.openDocument(conn, cmd, upsertRes);
}
}
@ -1382,6 +1402,7 @@ exports.install = function(server, callbackFunction) {
}
function* endAuth(conn, bIsRestore, documentCallbackUrl) {
var res = true;
var docId = conn.docId;
var tmpUser = conn.user;
connections.push(conn);
@ -1399,48 +1420,56 @@ exports.install = function(server, callbackFunction) {
}
// Отправляем на внешний callback только для тех, кто редактирует
var bindEventsRes = commonDefines.c_oAscServerCommandErrors.NoError;
if (!tmpUser.view) {
var userAction = new commonDefines.OutputAction(commonDefines.c_oAscUserAction.In, tmpUser.idOriginal);
// Если пришла информация о ссылке для посылания информации, то добавляем
if (documentCallbackUrl) {
yield* bindEvents(docId, documentCallbackUrl, conn.baseUrl, userAction);
bindEventsRes = yield* bindEvents(docId, documentCallbackUrl, conn.baseUrl, userAction);
} else {
yield* sendStatusDocument(docId, c_oAscChangeBase.No, userAction);
}
}
var lockDocument = null;
if (!bIsRestore && 2 === countNoView && !tmpUser.view) {
// Ставим lock на документ
var isLock = yield utils.promiseRedis(redisClient, redisClient.setnx,
redisKeyLockDoc + docId, JSON.stringify(firstParticipantNoView));
if(isLock) {
lockDocument = firstParticipantNoView;
yield utils.promiseRedis(redisClient, redisClient.expire, redisKeyLockDoc + docId, cfgExpLockDoc);
}
}
if (!lockDocument) {
var getRes = yield utils.promiseRedis(redisClient, redisClient.get, redisKeyLockDoc + docId);
if (getRes) {
lockDocument = JSON.parse(getRes);
}
}
if (lockDocument && !tmpUser.view) {
// Для view не ждем снятия lock-а
var sendObject = {
type: "waitAuth",
lockDocument: lockDocument
};
sendData(conn, sendObject);//Or 0 if fails
} else {
if (bIsRestore) {
yield* sendAuthInfo(undefined, undefined, conn, participantsMap);
} else {
var objChangesDocument = yield* getDocumentChanges(docId);
yield* sendAuthInfo(objChangesDocument.arrChanges, objChangesDocument.getLength(), conn, participantsMap);
if (commonDefines.c_oAscServerCommandErrors.NoError === bindEventsRes) {
var lockDocument = null;
if (!bIsRestore && 2 === countNoView && !tmpUser.view) {
// Ставим lock на документ
var isLock = yield utils.promiseRedis(redisClient, redisClient.setnx,
redisKeyLockDoc + docId, JSON.stringify(firstParticipantNoView));
if (isLock) {
lockDocument = firstParticipantNoView;
yield utils.promiseRedis(redisClient, redisClient.expire, redisKeyLockDoc + docId, cfgExpLockDoc);
}
}
if (!lockDocument) {
var getRes = yield utils.promiseRedis(redisClient, redisClient.get, redisKeyLockDoc + docId);
if (getRes) {
lockDocument = JSON.parse(getRes);
}
}
if (lockDocument && !tmpUser.view) {
// Для view не ждем снятия lock-а
var sendObject = {
type: "waitAuth",
lockDocument: lockDocument
};
sendData(conn, sendObject);//Or 0 if fails
} else {
if (bIsRestore) {
yield* sendAuthInfo(undefined, undefined, conn, participantsMap);
} else {
var objChangesDocument = yield* getDocumentChanges(docId);
yield* sendAuthInfo(objChangesDocument.arrChanges, objChangesDocument.getLength(), conn, participantsMap);
}
}
yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: docId, user: tmpUser, state: true}, docId, tmpUser.id);
} else {
sendFileError(conn, 'ip filter');
res = false;
}
yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: docId, user: tmpUser, state: true}, docId, tmpUser.id);
return res;
}
function* sendAuthInfo(objChangesDocument, changesIndex, conn, participantsMap) {
@ -1860,9 +1889,10 @@ exports.install = function(server, callbackFunction) {
function _checkLicense(conn) {
return co(function* () {
try {
const c_LR = constants.LICENSE_RESULT;
var licenseType = licenseInfo.type;
if (constants.LICENSE_RESULT.Success !== licenseType) {
licenseType = constants.LICENSE_RESULT.Success;
if (constants.PACKAGE_TYPE_OS === licenseInfo.packageType && c_LR.Error === licenseType) {
licenseType = c_LR.Success;
var count = constants.LICENSE_CONNECTIONS;
var cursor = '0', sum = 0, scanRes, tmp, length, i, users;
@ -1873,7 +1903,7 @@ exports.install = function(server, callbackFunction) {
for (i = 0; i < length; ++i) {
if (sum >= count) {
licenseType = constants.LICENSE_RESULT.Connections;
licenseType = c_LR.Connections;
break;
}
@ -1882,7 +1912,7 @@ exports.install = function(server, callbackFunction) {
}
if (sum >= count) {
licenseType = constants.LICENSE_RESULT.Connections;
licenseType = c_LR.Connections;
break;
}
@ -2057,8 +2087,13 @@ exports.install = function(server, callbackFunction) {
}
});
}
var innerPintJob = new cron.CronJob(cfgExpDocumentsCron, expireDoc);
innerPintJob.start();
var innerPingJob = function(opt_isStart) {
if (!opt_isStart) {
logger.warn('expireDoc restart');
}
new cron.CronJob(cfgExpDocumentsCron, expireDoc, innerPingJob, true);
};
innerPingJob(true);
pubsub = new pubsubService();
pubsub.on('message', pubsubOnMessage);
@ -2096,7 +2131,13 @@ exports.commandFromServer = function (req, res) {
logger.debug('Start commandFromServer: docId = %s c = %s', docId, query.c);
switch (query.c) {
case 'info':
result = yield* bindEvents(docId, query.callback, utils.getBaseUrlByRequest(req));
//If no files in the database means they have not been edited.
var selectRes = yield taskResult.select(docId);
if (selectRes.length > 0) {
result = yield* bindEvents(docId, query.callback, utils.getBaseUrlByRequest(req), undefined, query.userdata);
} else {
result = commonDefines.c_oAscServerCommandErrors.DocumentIdError;
}
break;
case 'drop':
if (query.userid) {

View File

@ -81,39 +81,8 @@ exports.loadTable = function (tableId, callbackFunction) {
var sqlCommand = "SELECT * FROM " + table + ";";
baseConnector.sqlQuery(sqlCommand, callbackFunction);
};
exports.upsertInTable = function (tableId, toInsert, toUpdate, callbackFunction) {
var table = getTableById(tableId);
var sqlCommand = "INSERT INTO " + table + " VALUES (";
for (var i = 0, l = toInsert.length; i < l; ++i) {
sqlCommand += baseConnector.sqlEscape(toInsert[i]);
if (i !== l - 1)
sqlCommand += ",";
}
sqlCommand += ") ON DUPLICATE KEY UPDATE ";
for (var i = 0, l = toUpdate.length; i + 1 < l; i += 2) {
sqlCommand += toUpdate[i] + "=" + baseConnector.sqlEscape(toUpdate[i+1]);
if (i + 1 !== l - 1)
sqlCommand += ",";
}
sqlCommand += ";";
baseConnector.sqlQuery(sqlCommand, callbackFunction);
};
exports.upsertInTablePromise = function (tableId, toInsert, toUpdate) {
return new Promise(function(resolve, reject) {
exports.upsertInTable(tableId, toInsert, toUpdate, function(error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
exports.insertCallback = function(id, href, baseUrl, callbackFunction) {
var sqlCommand = "INSERT IGNORE INTO " + tableCallbacks + " VALUES (" + baseConnector.sqlEscape(id) + "," +
baseConnector.sqlEscape(href) + "," + baseConnector.sqlEscape(baseUrl) + ");";
baseConnector.sqlQuery(sqlCommand, callbackFunction);
baseConnector.insertCallback(id, href, baseUrl, callbackFunction);
};
exports.insertCallbackPromise = function(id, href, baseUrl) {
return new Promise(function(resolve, reject) {
@ -266,6 +235,7 @@ exports.getChangesPromise = function (docId, optStartIndex, optEndIndex) {
if (null != optStartIndex && null != optEndIndex) {
getCondition += ' AND change_id>=' + optStartIndex + ' AND change_id<' + optEndIndex;
}
getCondition += ' ORDER BY change_id ASC';
getDataFromTable(c_oTableId.changes, "*", getCondition, function(error, result) {
if (error) {
reject(error);
@ -279,7 +249,7 @@ exports.getChanges = function (docId, callback) {
lockCriticalSection(docId, function () {_getChanges(docId, callback);});
};
function _getChanges (docId, callback) {
getDataFromTable(c_oTableId.changes, "*", "id='" + docId + "'",
getDataFromTable(c_oTableId.changes, "*", "id='" + docId + "' ORDER BY change_id ASC",
function (error, result) {unLockCriticalSection(docId); if (callback) callback(error, result);});
}

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,7 @@
var config = require('config');
var co = require('co');
const forwarded = require('forwarded');
var taskResult = require('./taskresult');
var logger = require('./../../Common/sources/logger');
var utils = require('./../../Common/sources/utils');
@ -74,7 +75,7 @@ function* getConvertStatus(cmd, selectRes, baseUrl) {
status.err = constants.UNKNOWN;
break;
case taskResult.FileStatus.NeedPassword:
status.err = constants.CONVERT_DRM;
status.err = row.status_info;
break;
}
var lastOpenDate = row.last_open_date;
@ -150,12 +151,13 @@ function convertHealthCheck(req, res) {
var docId = task.key;
//put test file to storage
var data = yield utils.readFile(cfgHealthCheckFilePath);
yield storage.putObject(docId + '/origin', data, data.length);
var format = 'docx';
yield storage.putObject(docId + '/origin.' + format, data, data.length);
//convert
var cmd = new commonDefines.InputCommand();
cmd.setCommand('conv');
cmd.setSaveKey(docId);
cmd.setFormat('docx');
cmd.setFormat(format);
cmd.setDocId(docId);
cmd.setTitle('Editor.bin');
cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_CANVAS);
@ -200,7 +202,7 @@ function convertRequest(req, res) {
cmd.setUrl(req.query['url']);
cmd.setEmbeddedFonts(false);//req.query['embeddedfonts'];
cmd.setFormat(req.query['filetype']);
var outputtype = req.query['outputtype'];
var outputtype = req.query['outputtype'] || '';
docId = 'conv_' + req.query['key'] + '_' + outputtype;
cmd.setDocId(docId);
cmd.setTitle(constants.OUTPUT_NAME + '.' + outputtype);
@ -211,8 +213,14 @@ function convertRequest(req, res) {
cmd.setPassword(req.query['password']);
var async = 'true' == req.query['async'];
var status = yield* convertByCmd(cmd, async, utils.getBaseUrlByRequest(req));
utils.fillXmlResponse(res, status.url, status.err);
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== cmd.getOutputFormat()) {
var status = yield* convertByCmd(cmd, async, utils.getBaseUrlByRequest(req));
utils.fillXmlResponse(res, status.url, status.err);
} else {
var addresses = forwarded(req);
logger.error('Error convert unknown outputtype: query = %s from = %s docId = %s', JSON.stringify(req.query), addresses, docId);
utils.fillXmlResponse(res, undefined, constants.UNKNOWN);
}
}
catch (e) {
logger.error('Error convert: docId = %s\r\n%s', docId, e.stack);

View File

@ -129,9 +129,18 @@ var checkDocumentExpire = function() {
}
});
};
var documentExpireJob = function(opt_isStart) {
if (!opt_isStart) {
logger.warn('checkDocumentExpire restart');
}
new cron.CronJob(cfgExpDocumentsCron, checkDocumentExpire, documentExpireJob, true);
};
documentExpireJob(true);
var documentExpireJob = new cron.CronJob(cfgExpDocumentsCron, checkDocumentExpire);
documentExpireJob.start();
var fileExpireJob = new cron.CronJob(cfgExpFilesCron, checkFileExpire);
fileExpireJob.start();
var fileExpireJob = function(opt_isStart) {
if (!opt_isStart) {
logger.warn('checkFileExpire restart');
}
new cron.CronJob(cfgExpFilesCron, checkFileExpire, fileExpireJob, true);
};
fileExpireJob(true);

View File

@ -1 +1 @@
/*
/*

View File

@ -31,29 +31,145 @@
*/
var pg = require('pg');
var co = require('co');
var pgEscape = require('pg-escape');
var types = require('pg').types;
var sqlBase = require('./baseConnector');
var configSql = require('config').get('services.CoAuthoring.sql');
var connectionString = 'postgres://' + configSql.get('dbUser') + ':' + configSql.get('dbPass') + '@' + configSql.get('dbHost') +
(configSql.get('dbPort') ? (':' + configSql.get('dbPort')) : '') + '/' + configSql.get('dbName');
var pool = new pg.Pool({
host: configSql.get('dbHost'),
port: configSql.get('dbPort'),
user: configSql.get('dbUser'),
password: configSql.get('dbPass'),
database: configSql.get('dbName'),
max: configSql.get('connectionlimit'),
min: 0,
ssl: false,
idleTimeoutMillis: 30000
});
var cfgTableCallbacks = configSql.get('tableCallbacks');
//todo datetime timezone
types.setTypeParser(1114, function(stringValue) {
return new Date(stringValue + '+0000');
});
types.setTypeParser(1184, function(stringValue) {
return new Date(stringValue + '+0000');
});
var logger = require('./../../Common/sources/logger');
exports.sqlQuery = function (sqlCommand, callbackFunction) {
pg.connect(connectionString, function (err, connection, done) {
if(err) {
logger.error('pool.getConnection error: %s', err);
if (callbackFunction) callbackFunction(err, null);
return;
}
connection.query(sqlCommand, function (error, result) {
//call `done()` to release the client back to the pool
done();
if (error) logger.error('sqlQuery: %s sqlCommand: %s', error.message, sqlCommand.slice(0, 50));
if (callbackFunction) callbackFunction(error, result ? result.rows : result);
});
});
exports.sqlQuery = function(sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog) {
co(function *() {
var client = null;
var result = null;
var error = null;
try {
client = yield pool.connect();
result = yield client.query(sqlCommand);
} catch (err) {
error = err;
if (!opt_noLog) {
if (client) {
logger.error('sqlQuery error sqlCommand: %s:\r\n%s', sqlCommand.slice(0, 50), err.stack);
} else {
logger.error('pool.getConnection error: %s', err);
}
}
} finally {
if (client) {
client.release();
}
if (callbackFunction) {
var output = result;
if (result && !opt_noModifyRes) {
if ('SELECT' === result.command) {
output = result.rows;
} else {
output = {affectedRows: result.rowCount};
}
}
callbackFunction(error, output);
}
}
});
};
exports.sqlEscape = function(value) {
//todo parameterized queries
return undefined !== value ? pgEscape.literal(value.toString()) : 'NULL';
};
var isSupportOnConflict = false;
(function checkIsSupportOnConflict() {
var sqlCommand = 'INSERT INTO checkIsSupportOnConflict (id) VALUES(1) ON CONFLICT DO NOTHING;';
exports.sqlQuery(sqlCommand, function(error, result) {
if (error) {
if ('42601' == error.code) {
//SYNTAX ERROR
isSupportOnConflict = false;
logger.debug('checkIsSupportOnConflict false');
} else if ('42P01' == error.code) {
// UNDEFINED TABLE
isSupportOnConflict = true;
logger.debug('checkIsSupportOnConflict true');
} else {
logger.error('checkIsSupportOnConflict unexpected error code:\r\n%s', error.stack);
}
}
}, true, true);
})();
exports.insertCallback = function(id, href, baseUrl, callbackFunction) {
var sqlCommand = "INSERT INTO " + cfgTableCallbacks + " VALUES (" + exports.sqlEscape(id) + "," +
exports.sqlEscape(href) + "," + exports.sqlEscape(baseUrl) + ")";
if (isSupportOnConflict) {
sqlCommand += ' ON CONFLICT DO NOTHING;';
exports.sqlQuery(sqlCommand, callbackFunction);
} else {
sqlCommand += ';';
exports.sqlQuery(sqlCommand, function(error, result) {
if (error && error.code == '23505') {
//UNIQUE VIOLATION
callbackFunction(null, result);
} else {
callbackFunction(error, result);
}
});
}
};
function getUpsertString(task) {
task.completeDefaults();
var dateNow = sqlBase.getDateTime(new Date());
var commandArg = [task.key, task.status, task.statusInfo, dateNow, task.title, task.userIndex, task.changeId];
var commandArgEsc = commandArg.map(function(curVal) {
return exports.sqlEscape(curVal)
});
if (isSupportOnConflict) {
//http://stackoverflow.com/questions/34762732/how-to-find-out-if-an-upsert-was-an-update-with-postgresql-9-5-upsert
return "INSERT INTO task_result (id, status, status_info, last_open_date, title, user_index, change_id) SELECT " +
commandArgEsc.join(', ') +
" WHERE 'false' = set_config('myapp.isupdate', 'false', true) ON CONFLICT (id) DO UPDATE SET last_open_date = " +
sqlBase.baseConnector.sqlEscape(dateNow) +
", user_index = task_result.user_index + 1 WHERE 'true' = set_config('myapp.isupdate', 'true', true) RETURNING" +
" current_setting('myapp.isupdate') as isupdate, user_index as userindex;";
} else {
return "SELECT * FROM merge_db(" + commandArgEsc.join(', ') + ");";
}
}
exports.upsert = function(task) {
return new Promise(function(resolve, reject) {
var sqlCommand = getUpsertString(task);
exports.sqlQuery(sqlCommand, function(error, result) {
if (error) {
reject(error);
} else {
if (result && result.rows.length > 0) {
var first = result.rows[0];
result = {affectedRows: 0, insertId: 0};
result.affectedRows = 'true' == first.isupdate ? 2 : 1;
result.insertId = first.userindex;
}
resolve(result);
}
}, true);
});
};
exports.sqlEscape = function (value) {
return value.replace( /(\')/g, "\\'" );
};

View File

@ -54,14 +54,16 @@ function init(pubsub, callback) {
pubsub.exchangePublish = yield rabbitMQCore.assertExchangePromise(pubsub.channelPublish, cfgRabbitExchangePubSub,
'fanout', {durable: true});
var channelReceive = yield rabbitMQCore.createChannelPromise(conn);
var queue = yield rabbitMQCore.assertQueuePromise(channelReceive, '', {autoDelete: true, exclusive: true});
channelReceive.bindQueue(queue, cfgRabbitExchangePubSub, '');
yield rabbitMQCore.consumePromise(channelReceive, queue, function (message) {
if (message) {
pubsub.emit('message', message.content.toString());
pubsub.channelReceive = yield rabbitMQCore.createChannelPromise(conn);
var queue = yield rabbitMQCore.assertQueuePromise(pubsub.channelReceive, '', {autoDelete: true, exclusive: true});
pubsub.channelReceive.bindQueue(queue, cfgRabbitExchangePubSub, '');
yield rabbitMQCore.consumePromise(pubsub.channelReceive, queue, function (message) {
if(null != pubsub.channelReceive){
if (message) {
pubsub.emit('message', message.content.toString());
}
pubsub.channelReceive.ack(message);
}
channelReceive.ack(message);
}, {noAck: false});
//process messages received while reconnection time
repeat(pubsub);
@ -76,6 +78,7 @@ function init(pubsub, callback) {
function clear(pubsub) {
pubsub.channelPublish = null;
pubsub.exchangePublish = null;
pubsub.channelReceive = null;
}
function repeat(pubsub) {
for (var i = 0; i < pubsub.publishStore.length; ++i) {
@ -92,6 +95,7 @@ function PubsubRabbitMQ() {
this.connection = null;
this.channelPublish = null;
this.exchangePublish = null;
this.channelReceive = null;
this.publishStore = [];
}
util.inherits(PubsubRabbitMQ, events.EventEmitter);

View File

@ -104,6 +104,8 @@ if (cluster.isMaster) {
const path = require('path');
const bodyParser = require("body-parser");
const mime = require('mime');
const forwarded = require('forwarded');
const ipaddr = require('ipaddr.js');
const docsCoServer = require('./DocsCoServer');
const canvasService = require('./canvasservice');
const converterService = require('./converterservice');
@ -112,6 +114,8 @@ 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;
@ -132,7 +136,7 @@ if (cluster.isMaster) {
var cfgStorageFolderName = configStorage.get('storageFolderName');
app.use('/' + cfgBucketName + '/' + cfgStorageFolderName, (req, res, next) => {
var index = req.url.lastIndexOf('/');
if (-1 != index) {
if ('GET' === req.method && -1 != index) {
var contentDisposition = req.query['disposition'] || 'attachment';
var sendFileOptions = {
root: configStorage.get('fs.folderPath'),
@ -154,11 +158,30 @@ if (cluster.isMaster) {
}
});
} else {
req.sendStatus(404)
res.sendStatus(404)
}
});
}
function checkClientIp(req, res, next) {
var status = 0;
if (cfgIpFilterEseForRequest) {
var addresses = forwarded(req);
var ipString = addresses[addresses.length - 1];
//IPv6 -> IPv4
if (ipaddr.IPv6.isValid(ipString)) {
var ip = ipaddr.IPv6.parse(ipString);
if (ip.isIPv4MappedAddress()) {
ipString = ip.toIPv4Address().toString();
}
}
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
@ -171,8 +194,8 @@ if (cluster.isMaster) {
res.send('Server is functioning normally. Version: ' + docsCoServer.version);
});
app.get('/coauthoring/CommandService.ashx', docsCoServer.commandFromServer);
app.post('/coauthoring/CommandService.ashx', docsCoServer.commandFromServer);
app.get('/coauthoring/CommandService.ashx', checkClientIp, docsCoServer.commandFromServer);
app.post('/coauthoring/CommandService.ashx', checkClientIp, docsCoServer.commandFromServer);
if (config.has('server.fonts_route')) {
var fontsRoute = config.get('server.fonts_route');
@ -181,12 +204,12 @@ if (cluster.isMaster) {
app.get('/' + fontsRoute + 'odttf/:fontname', fontService.getFont);
}
app.get('/ConvertService.ashx', converterService.convert);
app.post('/ConvertService.ashx', converterService.convert);
app.get('/ConvertService.ashx', checkClientIp, converterService.convert);
app.post('/ConvertService.ashx', checkClientIp, converterService.convert);
var rawFileParser = bodyParser.raw({ inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*' });
app.get('/FileUploader.ashx', rawFileParser, fileUploaderService.uploadTempFile);
app.post('/FileUploader.ashx', rawFileParser, fileUploaderService.uploadTempFile);
app.get('/FileUploader.ashx', checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
app.post('/FileUploader.ashx', checkClientIp, rawFileParser, fileUploaderService.uploadTempFile);
var docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i');
app.param('docid', (req, res, next, val) => {
@ -207,7 +230,7 @@ if (cluster.isMaster) {
app.post('/upload/:docid/:userid/:index/:vkey?', rawFileParser, fileUploaderService.uploadImageFile);
app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs);
app.get('/healthcheck', converterService.convertHealthCheck);
app.get('/healthcheck', checkClientIp, converterService.convertHealthCheck);
});
process.on('message', (msg) => {

View File

@ -83,36 +83,8 @@ TaskResultData.prototype.completeDefaults = function() {
}
};
function getUpsertString(task, opt_updateUserIndex) {
task.completeDefaults();
var dateNow = sqlBase.getDateTime(new Date());
var commandArg = [task.key, task.status, task.statusInfo, dateNow, task.title, task.userIndex, task.changeId];
var commandArgEsc = commandArg.map(function(curVal) {
return sqlBase.baseConnector.sqlEscape(curVal)
});
var sql = 'INSERT INTO task_result ( id, status, status_info, last_open_date, title,' +
' user_index, change_id ) VALUES (' + commandArgEsc.join(', ') + ') ON DUPLICATE KEY UPDATE' +
' last_open_date = ' + sqlBase.baseConnector.sqlEscape(dateNow);
if (opt_updateUserIndex) {
//todo LAST_INSERT_ID in posgresql - RETURNING
sql += ', user_index = LAST_INSERT_ID(user_index + 1);';
} else {
sql += ';';
}
return sql;
}
function upsert(task, opt_updateUserIndex) {
return new Promise(function(resolve, reject) {
var sqlCommand = getUpsertString(task, opt_updateUserIndex);
sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
return sqlBase.baseConnector.upsert(task, opt_updateUserIndex);
}
function getSelectString(docId) {
@ -281,7 +253,6 @@ exports.upsert = upsert;
exports.select = select;
exports.update = update;
exports.updateIf = updateIf;
exports.addRandomKey = addRandomKey;
exports.addRandomKeyTask = addRandomKeyTask;
exports.remove = remove;
exports.getExpired = getExpired;

View File

@ -5,6 +5,6 @@
"private": true,
"dependencies": {
"co": "^4.6.0",
"config": "^1.19.0"
"config": "^1.21.0"
}
}

View File

@ -66,10 +66,9 @@ var MAX_OPEN_FILES = 200;
var TEMP_PREFIX = 'ASC_CONVERT';
var queue = null;
var clientStatsD = statsDClient.getClient();
var exitCodesReturn = [constants.CONVERT_MS_OFFCRYPTO, constants.CONVERT_NEED_PARAMS, constants.CONVERT_CORRUPTED,
constants.CONVERT_DRM, constants.CONVERT_PASSWORD];
var exitCodesMinorError = [constants.CONVERT_MS_OFFCRYPTO, constants.CONVERT_NEED_PARAMS, constants.CONVERT_DRM,
var exitCodesReturn = [constants.CONVERT_NEED_PARAMS, constants.CONVERT_CORRUPTED, constants.CONVERT_DRM,
constants.CONVERT_PASSWORD];
var exitCodesMinorError = [constants.CONVERT_NEED_PARAMS, constants.CONVERT_DRM, constants.CONVERT_PASSWORD];
var exitCodesUpload = [constants.NO_ERROR, constants.CONVERT_CORRUPTED, constants.CONVERT_NEED_PARAMS,
constants.CONVERT_DRM];
@ -167,24 +166,31 @@ function* downloadFile(docId, uri, fileFrom) {
var res = false;
var data = null;
var downloadAttemptCount = 0;
while (!res && downloadAttemptCount++ < cfgDownloadAttemptMaxCount) {
try {
data = yield utils.downloadUrlPromise(uri, cfgDownloadTimeout * 1000, cfgDownloadMaxBytes);
res = true;
} catch (err) {
res = false;
logger.error('error downloadFile:url=%s;attempt=%d;(id=%s)\r\n%s', uri, downloadAttemptCount, docId, err.stack);
//not continue attempts if timeout
if (err.code === 'ETIMEDOUT' || err.code === 'EMSGSIZE') {
break;
} else {
yield utils.sleep(cfgDownloadAttemptDelay);
var urlParsed = url.parse(uri);
var filterStatus = utils.checkIpFilter(urlParsed.hostname);
if (0 == filterStatus) {
while (!res && downloadAttemptCount++ < cfgDownloadAttemptMaxCount) {
try {
data = yield utils.downloadUrlPromise(uri, cfgDownloadTimeout * 1000, cfgDownloadMaxBytes);
res = true;
} catch (err) {
res = false;
logger.error('error downloadFile:url=%s;attempt=%d;code:%s;connect:%s;(id=%s)\r\n%s', uri, downloadAttemptCount, err.code, err.connect, docId, err.stack);
//not continue attempts if timeout
if (err.code === 'ETIMEDOUT' || err.code === 'EMSGSIZE') {
break;
} else {
yield utils.sleep(cfgDownloadAttemptDelay);
}
}
}
}
if (res) {
logger.debug('downloadFile complete(id=%s)', docId);
fs.writeFileSync(fileFrom, data);
if (res) {
logger.debug('downloadFile complete(id=%s)', docId);
fs.writeFileSync(fileFrom, data);
}
} else {
logger.error('checkIpFilter error:url=%s;code:%s;(id=%s)', uri, filterStatus, docId);
res = false;
}
return res;
}
@ -231,17 +237,6 @@ function* downloadFileFromStorage(id, strPath, dir) {
fs.writeFileSync(path.join(dir, fileRel), data);
}
}
function pipeFile(fsFrom, fsTo) {
return new Promise(function(resolve, reject) {
fsFrom.pipe(fsTo, {end: false});
fsFrom.on('end', function() {
resolve();
});
fsFrom.on('error', function(e) {
reject(e);
});
});
}
function* processDownloadFromStorage(dataConvert, cmd, task, tempDirs) {
if (task.getFromOrigin() || task.getFromSettings()) {
dataConvert.fileFrom = path.join(tempDirs.source, 'origin.' + cmd.getFormat());
@ -262,7 +257,7 @@ function* processDownloadFromStorage(dataConvert, cmd, task, tempDirs) {
fsFullFile = yield utils.promiseCreateWriteStream(dataConvert.fileFrom);
}
var fsCurFile = yield utils.promiseCreateReadStream(file);
yield pipeFile(fsCurFile, fsFullFile);
yield utils.pipeStreams(fsCurFile, fsFullFile, false);
}
}
if (fsFullFile) {
@ -473,7 +468,7 @@ function* ExecuteTask(task) {
if (constants.NO_ERROR === error) {
if(constants.AVS_OFFICESTUDIO_FILE_OTHER_HTMLZIP === dataConvert.formatTo && cmd.getSaveKey() && !dataConvert.mailMergeSend) {
//todo заглушка.вся конвертация на клиенте, но нет простого механизма сохранения на клиенте
fs.writeFileSync(dataConvert.fileTo, fs.readFileSync(dataConvert.fileFrom));
yield utils.pipeFiles(dataConvert.fileFrom, dataConvert.fileTo);
} else {
var paramsFile = path.join(tempDirs.temp, 'params.xml');
dataConvert.serialize(paramsFile);

View File

@ -1,5 +1,6 @@
[![License](https://img.shields.io/badge/License-GNU%20AGPL%20V3-green.svg?style=flat)](http://www.gnu.org/licenses/agpl-3.0.ru.html) ![Release](https://img.shields.io/badge/Release-v4.0.3-blue.svg?style=flat)
[![License](https://img.shields.io/badge/License-GNU%20AGPL%20V3-green.svg?style=flat)](http://www.gnu.org/licenses/agpl-3.0.ru.html) ![Release](https://img.shields.io/badge/Release-v4.1.0-blue.svg?style=flat)
## Server
The backend server software layer which is the part of [ONLYOFFICE Document Server][2] and is the base for all other components.
@ -74,10 +75,11 @@ In case it is necessary to temporarily edit the config files, create the local.j
## User Feedback and Support
If you have any problems with or questions about [ONLYOFFICE Document Server][2], please visit our official forum to find answers to your questions: [dev.onlyoffice.org][1].
If you have any problems with or questions about [ONLYOFFICE Document Server][2], please visit our official forum to find answers to your questions: [dev.onlyoffice.org][1] or you can ask and answer ONLYOFFICE development questions on [Stack Overflow][3].
[1]: http://dev.onlyoffice.org
[2]: https://github.com/ONLYOFFICE/DocumentServer
[3]: http://stackoverflow.com/questions/tagged/onlyoffice
## License

View File

@ -4,9 +4,9 @@
"homepage": "http://www.onlyoffice.com",
"private": true,
"dependencies": {
"express" : "^4.13.4",
"sockjs" : "^0.3.15",
"nodehun" : "^2.0.8",
"config": "^1.19.0"
"express" : "^4.14.0",
"sockjs" : "^0.3.17",
"nodehun" : "^2.0.10",
"config" : "^1.21.0"
}
}

View File

@ -0,0 +1,81 @@
--
-- Create schema onlyoffice
--
-- CREATE DATABASE onlyoffice ENCODING = 'UTF8' CONNECTION LIMIT = -1;
--
-- Drop tables
--
DROP TABLE IF EXISTS "public"."doc_callbacks";
DROP TABLE IF EXISTS "public"."doc_changes";
-- ----------------------------
-- Table structure for doc_callbacks
-- ----------------------------
CREATE TABLE IF NOT EXISTS "public"."doc_callbacks" (
"id" varchar(255) COLLATE "default" NOT NULL,
"callback" text COLLATE "default" NOT NULL,
"baseurl" text COLLATE "default" NOT NULL,
PRIMARY KEY ("id")
)
WITH (OIDS=FALSE);
-- ----------------------------
-- Table structure for doc_changes
-- ----------------------------
CREATE TABLE IF NOT EXISTS "public"."doc_changes" (
"id" varchar(255) COLLATE "default" NOT NULL,
"change_id" int8 NOT NULL,
"user_id" varchar(255) COLLATE "default" NOT NULL,
"user_id_original" varchar(255) COLLATE "default" NOT NULL,
"user_name" varchar(255) COLLATE "default" NOT NULL,
"change_data" text COLLATE "default" NOT NULL,
"change_date" timestamp without time zone NOT NULL,
PRIMARY KEY ("id", "change_id")
)
WITH (OIDS=FALSE);
-- ----------------------------
-- Table structure for task_result
-- ----------------------------
CREATE TABLE IF NOT EXISTS "public"."task_result" (
"id" varchar(255) COLLATE "default" NOT NULL,
"status" int2 NOT NULL,
"status_info" int8 NOT NULL,
"last_open_date" timestamp without time zone NOT NULL,
"title" varchar(255) COLLATE "default" NOT NULL,
"user_index" int8 NOT NULL DEFAULT 1,
"change_id" int8 NOT NULL DEFAULT 0,
PRIMARY KEY ("id")
)
WITH (OIDS=FALSE);
--https://www.postgresql.org/docs/current/static/plpgsql-control-structures.html#PLPGSQL-UPSERT-EXAMPLE
CREATE OR REPLACE FUNCTION merge_db(_id varchar(255), _status int2, _status_info int8, _last_open_date timestamp without time zone, _title varchar(255), _user_index int8, _change_id int8, OUT isupdate char(5), OUT userindex int8) AS
$$
DECLARE
t_var "public"."task_result"."user_index"%TYPE;
BEGIN
LOOP
-- first try to update the key
-- note that "a" must be unique
UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1 WHERE id = _id RETURNING user_index into userindex;
IF found THEN
isupdate := 'true';
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO "public"."task_result"(id, status, status_info, last_open_date, title, user_index, change_id) VALUES(_id, _status, _status_info, _last_open_date, _title, _user_index, _change_id) RETURNING user_index into userindex;
isupdate := 'false';
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the UPDATE again
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;

View File

@ -0,0 +1 @@
DROP DATABASE IF EXISTS onlyoffice;