Merge branch release/v7.3.0 into master

This commit is contained in:
papacarlo
2023-01-31 08:01:36 +00:00
35 changed files with 1932 additions and 819 deletions

View File

@ -28,11 +28,11 @@ License: MIT License
License File: license/Megapixel.license
5. SockJS - WebSocket emulation - Javascript client
5. SocketIO - WebSocket emulation - Javascript client
URL: http://sockjs.org
URL: https://socket.io
License: MIT License
License File: license/SockJS.license
License File: license/SocketIO.license
6. Underscore - Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects. It's the tie to go along with jQuery's tux, and Backbone.js's suspenders.

View File

@ -73,6 +73,10 @@
"passwords": ["verysecretstring"]
}
},
"bottleneck": {
"getChanges": {
}
},
"wopi": {
"enable": false,
"host" : "",
@ -82,7 +86,8 @@
"favIconUrlCell" : "/web-apps/apps/spreadsheeteditor/main/resources/img/favicon.ico",
"favIconUrlSlide" : "/web-apps/apps/presentationeditor/main/resources/img/favicon.ico",
"fileInfoBlockList" : ["FileUrl"],
"wordView": ["pdf", "djvu", "xps", "oxps", "doc", "dotx", "dotm", "dot", "fodt", "ott", "rtf", "mht", "html", "htm", "xml", "epub", "fb2"],
"pdfView": ["pdf", "djvu", "xps", "oxps"],
"wordView": ["doc", "dotx", "dotm", "dot", "fodt", "ott", "rtf", "mht", "html", "htm", "xml", "epub", "fb2"],
"wordEdit": ["docx", "docm", "docxf", "oform", "odt", "txt"],
"cellView": ["xls", "xlsb", "xltx", "xltm", "xlt", "fods", "ots"],
"cellEdit": ["xlsx", "xlsm", "ods", "csv"],
@ -136,7 +141,7 @@
"editorDataStorage": "editorDataMemory",
"assemblyFormatAsOrigin": true,
"newFileTemplate" : "../../document-templates/new",
"downloadFileAllowExt": ["pdf"],
"downloadFileAllowExt": ["pdf", "xlsx"],
"tokenRequiredParams": true
},
"requestDefaults": {
@ -179,7 +184,7 @@
"options": {}
},
"pubsub": {
"maxChanges": 1000
"maxChanges": 0
},
"expire": {
"saveLock": 60,
@ -259,6 +264,7 @@
"attempts": 50,
"delay": "2s"
},
"binaryChanges": false,
"websocketMaxPayloadSize": "1.5MB",
"maxChangesSize": "0mb"
},
@ -267,6 +273,15 @@
"disable_cors": true,
"websocket": true
},
"socketio": {
"connection": {
"path": "/doc/",
"serveClient": false,
"pingTimeout": 20000,
"pingInterval": 25000,
"maxHttpBufferSize": 1e8
}
},
"callbackBackoffOptions": {
"retries": 0,
"timeout":{

View File

@ -41,6 +41,12 @@
},
"sockjs": {
"sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js"
},
"socketio": {
"connection": {
"pingTimeout": 86400000,
"pingInterval": 86400000
}
}
}
},

View File

@ -47,6 +47,12 @@
},
"sockjs": {
"sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js"
},
"socketio": {
"connection": {
"pingTimeout": 86400000,
"pingInterval": 86400000
}
}
}
},

View File

@ -47,6 +47,12 @@
},
"sockjs": {
"sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js"
},
"socketio": {
"connection": {
"pingTimeout": 86400000,
"pingInterval": 86400000
}
}
}
},

View File

@ -163,7 +163,7 @@
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"buffer-more-ints": {
"version": "1.0.0",
@ -228,9 +228,9 @@
"integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ=="
},
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "^2.1.1"
}
@ -259,9 +259,9 @@
}
},
"ecdsa-sig-formatter": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz",
"integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=",
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
@ -429,9 +429,9 @@
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json5": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"requires": {
"minimist": "^1.2.0"
}
@ -445,19 +445,14 @@
}
},
"jsonwebtoken": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz",
"integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==",
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
"integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
"requires": {
"jws": "^3.1.5",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1"
"jws": "^3.2.2",
"lodash": "^4.17.21",
"ms": "^2.1.1",
"semver": "^7.3.8"
}
},
"jsprim": {
@ -479,21 +474,21 @@
}
},
"jwa": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz",
"integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.10",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz",
"integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==",
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.1.5",
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
@ -515,41 +510,6 @@
"lodash._baseclone": "~4.5.0"
}
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"log4js": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz",
@ -577,6 +537,14 @@
}
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"make-dir": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
@ -782,9 +750,9 @@
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
},
"rhea": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/rhea/-/rhea-0.3.9.tgz",
"integrity": "sha512-16mqaARtj9ZgiYmD5F9uwcgvy5pOB64mWIbkBgrje4zEByIBjHdYB25EyeWb3baiI9OYAYMWb2rnqBfyv5weOg==",
"version": "1.0.24",
"resolved": "https://registry.npmjs.org/rhea/-/rhea-1.0.24.tgz",
"integrity": "sha512-PEl62U2EhxCO5wMUZ2/bCBcXAVKN9AdMSNQOrp3+R5b77TEaOSiy16MQ0sIOmzj/iqsgIAgPs1mt3FYfu1vIXA==",
"requires": {
"debug": "0.8.0 - 3.5.0"
}
@ -804,6 +772,14 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"semver": {
"version": "7.3.8",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
"requires": {
"lru-cache": "^6.0.0"
}
},
"slide": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
@ -1032,6 +1008,11 @@
"version": "9.0.7",
"resolved": "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

View File

@ -14,7 +14,7 @@
"forwarded": "^0.1.2",
"fs-extra": "^7.0.0",
"ipaddr.js": "^1.8.1",
"jsonwebtoken": "^8.3.0",
"jsonwebtoken": "^9.0.0",
"log4js": "^6.4.1",
"mime": "^2.3.1",
"mkdirp": "^0.5.1",
@ -24,7 +24,7 @@
"openpgp": "^4.10.8",
"request": "^2.88.0",
"request-filtering-agent": "^1.0.5",
"rhea": "^0.3.9",
"rhea": "^1.0.24",
"uri-js": "^4.2.2",
"win-ca": "^3.5.0"
}

View File

@ -75,19 +75,13 @@ function connetPromise(reconnectOnConnectionError, closeCallback) {
startConnect();
});
}
function openSenderPromise(conn, name) {
function openSenderPromise(conn, options) {
return new Promise(function(resolve, reject) {
let options = {target: name};
resolve(conn.open_sender(options));
});
}
function openReceiverPromise(conn, name, autoaccept) {
function openReceiverPromise(conn, options) {
return new Promise(function(resolve, reject) {
let options = {source: name};
if (!autoaccept) {
options.credit_window = 0;
options.autoaccept = false;
}
resolve(conn.open_receiver(options));
});
}

View File

@ -1110,6 +1110,17 @@ const c_oAscUnlockRes = {
Unlocked: 1,
Empty: 2
};
const FileStatus = {
None: 0,
Ok: 1,
WaitQueue: 2,
NeedParams: 3,
Err: 5,
ErrToReload: 6,
SaveVersion: 7,
UpdateVersion: 8,
NeedPassword: 9
};
const buildVersion = '4.1.2';
const buildNumber = 37;
@ -1136,5 +1147,6 @@ exports.c_oAscUrlTypes = c_oAscUrlTypes;
exports.c_oAscSecretType = c_oAscSecretType;
exports.c_oAscQueueType = c_oAscQueueType;
exports.c_oAscUnlockRes = c_oAscUnlockRes;
exports.FileStatus = FileStatus;
exports.buildVersion = buildVersion;
exports.buildNumber = buildNumber;

View File

@ -49,6 +49,7 @@ exports.CHANGES_NAME = 'changes';
exports.VIEWER_ONLY = /^(?:(pdf|djvu|xps|oxps))$/;
exports.DEFAULT_DOC_ID = 'docId';
exports.DEFAULT_USER_ID = 'userId';
exports.ALLOWED_PROTO = /^https?$/i;
exports.RIGHTS = {
None : 0,
@ -295,7 +296,7 @@ exports.RESTORE = 'no cache';
exports.CONTENT_DISPOSITION_INLINE = 'inline';
exports.CONTENT_DISPOSITION_ATTACHMENT = 'attachment';
exports.CONN_CLOSED = 3;
exports.CONN_CLOSED = "closed";
exports.FILE_STATUS_OK = 'ok';
exports.FILE_STATUS_UPDATE_VERSION = 'updateversion';

View File

@ -53,7 +53,8 @@ Context.prototype.initFromConnection = function(conn) {
let tenant = tenantManager.getTenantByConnection(this, conn);
let docId = conn.docid;
if (!docId) {
const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(conn.url);
let handshake = conn.handshake;
const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(handshake.url);
if (docIdParsed && 1 < docIdParsed.length) {
docId = docIdParsed[1];
}

View File

@ -176,13 +176,34 @@ function initActive(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAdd
});
taskqueue.connection = conn;
if (isAddTask) {
taskqueue.channelConvertTask = yield activeMQCore.openSenderPromise(conn, cfgActiveQueueConvertTask);
//https://github.com/amqp/rhea/issues/251#issuecomment-535076570
let optionsConvertTask = {
target: {
address: cfgActiveQueueConvertTask,
capabilities: ['queue']
}
};
taskqueue.channelConvertTask = yield activeMQCore.openSenderPromise(conn, optionsConvertTask);
}
if (isAddResponse) {
taskqueue.channelConvertResponse = yield activeMQCore.openSenderPromise(conn, cfgActiveQueueConvertResponse);
let optionsConvertResponse = {
target: {
address: cfgActiveQueueConvertResponse,
capabilities: ['queue']
}
};
taskqueue.channelConvertResponse = yield activeMQCore.openSenderPromise(conn, optionsConvertResponse);
}
if (isAddTaskReceive) {
let receiver = yield activeMQCore.openReceiverPromise(conn, cfgActiveQueueConvertTask, false);
let optionsConvertTask = {
source: {
address: cfgActiveQueueConvertTask,
capabilities: ['queue']
},
credit_window: 0,
autoaccept: false
};
let receiver = yield activeMQCore.openReceiverPromise(conn, optionsConvertTask);
//todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1
receiver.add_credit(1);
receiver.on("message", function(context) {
@ -202,7 +223,15 @@ function initActive(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAdd
taskqueue.channelConvertTaskReceive = receiver;
}
if (isAddResponseReceive) {
let receiver = yield activeMQCore.openReceiverPromise(conn, cfgActiveQueueConvertResponse, false);
let optionsConvertResponse = {
source: {
address: cfgActiveQueueConvertResponse,
capabilities: ['queue']
},
credit_window: 0,
autoaccept: false
};
let receiver = yield activeMQCore.openReceiverPromise(conn, optionsConvertResponse);
//todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1
receiver.add_credit(1);
receiver.on("message", function(context) {
@ -216,10 +245,24 @@ function initActive(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAdd
taskqueue.channelConvertResponseReceive = receiver;
}
if (isAddDelayed) {
taskqueue.channelDelayed = yield activeMQCore.openSenderPromise(conn, cfgActiveQueueDelayed);
let optionsDelayed = {
target: {
address: cfgActiveQueueDelayed,
capabilities: ['queue']
}
};
taskqueue.channelDelayed = yield activeMQCore.openSenderPromise(conn, optionsDelayed);
}
if (isEmitDead) {
let receiver = yield activeMQCore.openReceiverPromise(conn, cfgActiveQueueConvertDead, false);
let optionsConvertDead = {
source: {
address: cfgActiveQueueConvertDead,
capabilities: ['queue']
},
credit_window: 0,
autoaccept: false
};
let receiver = yield activeMQCore.openReceiverPromise(conn, optionsConvertDead);
//todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1
receiver.add_credit(1);
receiver.on("message", function(context) {

View File

@ -669,9 +669,9 @@ function getDomain(hostHeader, forwardedHostHeader) {
};
function getBaseUrl(protocol, hostHeader, forwardedProtoHeader, forwardedHostHeader, forwardedPrefixHeader) {
var url = '';
if (forwardedProtoHeader) {
if (forwardedProtoHeader && constants.ALLOWED_PROTO.test(forwardedProtoHeader)) {
url += forwardedProtoHeader;
} else if (protocol) {
} else if (protocol && constants.ALLOWED_PROTO.test(protocol)) {
url += protocol;
} else {
url += 'http';
@ -684,16 +684,22 @@ function getBaseUrl(protocol, hostHeader, forwardedProtoHeader, forwardedHostHea
return url;
}
function getBaseUrlByConnection(conn) {
return getBaseUrl('', conn.headers['host'], conn.headers['x-forwarded-proto'], conn.headers['x-forwarded-host'], conn.headers['x-forwarded-prefix']);
conn = conn.request;
//Header names are lower-cased. https://nodejs.org/api/http.html#messageheaders
let proto = conn.headers['x-forwarded-proto'] || conn.headers['cloudfront-forwarded-proto'];
return getBaseUrl('', conn.headers['host'], proto, conn.headers['x-forwarded-host'], conn.headers['x-forwarded-prefix']);
}
function getBaseUrlByRequest(req) {
return getBaseUrl(req.protocol, req.get('host'), req.get('x-forwarded-proto'), req.get('x-forwarded-host'), req.get('x-forwarded-prefix'));
//case-insensitive match. https://expressjs.com/en/api.html#req.get
let proto = req.get('x-forwarded-proto') || req.get('cloudfront-forwarded-proto');
return getBaseUrl(req.protocol, req.get('host'), proto, req.get('x-forwarded-host'), req.get('x-forwarded-prefix'));
}
exports.getBaseUrlByConnection = getBaseUrlByConnection;
exports.getBaseUrlByRequest = getBaseUrlByRequest;
function getDomainByConnection(ctx, conn) {
let host = conn.headers['host'];
let forwardedHost = conn.headers['x-forwarded-host'];
let incomingMessage = conn.request;
let host = incomingMessage.headers['host'];
let forwardedHost = incomingMessage.headers['x-forwarded-host'];
ctx.logger.debug("getDomainByConnection headers['host']=%s headers['x-forwarded-host']=%s", host, forwardedHost);
return getDomain(host, forwardedHost);
}
@ -1039,3 +1045,9 @@ exports.getFunctionArguments = function(func) {
join('').
split(/\s*,\s*/);
};
exports.isUselesSfc = function(row, cmd) {
return !(row && commonDefines.FileStatus.SaveVersion === row.status && cmd.getStatusInfoIn() === row.status_info);
};
exports.getChangesFileHeader = function() {
return `CHANGES\t${commonDefines.buildVersion}\n`;
};

File diff suppressed because it is too large Load Diff

View File

@ -12,16 +12,17 @@
"ajv": "^8.9.0",
"apicache": "^1.6.2",
"base64-stream": "^1.0.0",
"body-parser": "^1.18.3",
"body-parser": "^1.20.1",
"bottleneck": "^2.19.5",
"bytes": "^3.0.0",
"co": "^4.6.0",
"config": "^2.0.1",
"cron": "^1.5.0",
"deep-equal": "^1.0.1",
"ejs": "~2.5.1",
"express": "^4.17.1",
"ejs": "^3.1.8",
"express": "^4.18.2",
"fakeredis": "^2.0.0",
"jsonwebtoken": "^8.3.0",
"jsonwebtoken": "^9.0.0",
"jwa": "^1.1.6",
"mime": "^2.3.1",
"mime-db": "^1.49.0",
@ -30,13 +31,14 @@
"multi-integer-range": "^4.0.7",
"multiparty": "^4.2.1",
"mysql2": "^2.3.3",
"pg": "^8.5.1",
"pg": "^8.8.0",
"redis": "^2.8.0",
"retry": "^0.12.0",
"sockjs": "^0.3.21",
"socket.io": "^4.5.2",
"underscore": "^1.13.1",
"utf7": "^1.0.2",
"windows-locale": "^1.0.1"
"windows-locale": "^1.0.1",
"xmlbuilder2": "^3.0.2"
},
"pkg": {
"scripts": [

View File

@ -72,7 +72,7 @@
'use strict';
const sockjs = require('sockjs');
const { Server } = require("socket.io");
const _ = require('underscore');
const url = require('url');
const os = require('os');
@ -130,7 +130,6 @@ const cfgExpSessionIdle = ms(config.get('expire.sessionidle'));
const cfgExpSessionAbsolute = ms(config.get('expire.sessionabsolute'));
const cfgExpSessionCloseCommand = ms(config.get('expire.sessionclosecommand'));
const cfgExpUpdateVersionStatus = ms(config.get('expire.updateVersionStatus'));
const cfgSockjs = config.get('sockjs');
const cfgTokenEnableBrowser = config.get('token.enable.browser');
const cfgTokenEnableRequestInbox = config.get('token.enable.request.inbox');
const cfgTokenSessionAlgorithm = config.get('token.session.algorithm');
@ -150,6 +149,8 @@ const cfgErrorFiles = configCommon.get('FileConverter.converter.errorfiles');
const cfgOpenProtectedFile = config.get('server.openProtectedFile');
const cfgRefreshLockInterval = ms(configCommon.get('wopi.refreshLockInterval'));
const cfgTokenRequiredParams = config.get('server.tokenRequiredParams');
const cfgSocketIoConnection = configCommon.get('services.CoAuthoring.socketio.connection');
const cfgTableResult = configCommon.get('services.CoAuthoring.sql.tableResult');
const EditorTypes = {
document : 0,
@ -506,7 +507,7 @@ function fillJwtByConnection(ctx, conn) {
}
function sendData(ctx, conn, data) {
conn.write(JSON.stringify(data));
conn.emit('message', data);
const type = data ? data.type : null;
ctx.logger.debug('sendData: type = %s', type);
}
@ -535,6 +536,13 @@ function sendDataRefreshToken(ctx, conn, msg) {
function sendDataRpc(ctx, conn, responseKey, data) {
sendData(ctx, conn, {type: "rpc", responseKey: responseKey, data: data});
}
function sendDataDrop(ctx, conn, code, description) {
sendData(ctx, conn, {type: "drop", code: code, description: description});
}
function sendDataDisconnectReason(ctx, conn, code, description) {
sendData(ctx, conn, {type: "disconnectReason", code: code, description: description});
}
function sendReleaseLock(ctx, conn, userLocks) {
sendData(ctx, conn, {type: "releaseLock", locks: _.map(userLocks, function(e) {
return {
@ -1028,13 +1036,15 @@ function* publishCloseUsersConnection(ctx, docId, users, isOriginalId, code, des
});
}
}
function closeUsersConnection(docId, usersMap, isOriginalId, code, description) {
let elConnection;
function closeUsersConnection(ctx, docId, usersMap, isOriginalId, code, description) {
//close
let conn;
for (let i = connections.length - 1; i >= 0; --i) {
elConnection = connections[i];
if (elConnection.docId === docId) {
if (isOriginalId ? usersMap[elConnection.user.idOriginal] : usersMap[elConnection.user.id]) {
elConnection.close(code, description);
conn = connections[i];
if (conn.docId === docId) {
if (isOriginalId ? usersMap[conn.user.idOriginal] : usersMap[conn.user.id]) {
sendDataDisconnectReason(ctx, conn, code, description);
conn.disconnect(true);
}
}
}
@ -1050,11 +1060,7 @@ function dropUserFromDocument(ctx, docId, userId, description) {
for (var i = 0, length = connections.length; i < length; ++i) {
elConnection = connections[i];
if (elConnection.docId === docId && userId === elConnection.user.idOriginal && !elConnection.isCloseCoAuthoring) {
sendData(ctx, elConnection,
{
type: "drop",
description: description
});//Or 0 if fails
sendDataDrop(ctx, elConnection, description);
}
}
}
@ -1098,7 +1104,9 @@ let unlockWopiDoc = co.wrap(function*(ctx, docId, opt_userIndex) {
if (getRes && getRes.wopiParams && getRes.wopiParams.userAuth && 'view' !== getRes.wopiParams.userAuth.mode) {
yield wopiClient.unlock(ctx, getRes.wopiParams);
let unlockInfo = wopiClient.getWopiUnlockMarker(getRes.wopiParams);
yield canvasService.commandOpenStartPromise(ctx, docId, undefined, true, unlockInfo);
if (unlockInfo) {
yield canvasService.commandOpenStartPromise(ctx, docId, undefined, true, unlockInfo);
}
}
});
function* cleanDocumentOnExit(ctx, docId, deleteChanges, opt_userIndex) {
@ -1106,7 +1114,7 @@ function* cleanDocumentOnExit(ctx, docId, deleteChanges, opt_userIndex) {
yield editorData.cleanDocumentOnExit(ctx, docId);
//remove changes
if (deleteChanges) {
yield taskResult.restoreInitialPassword(ctx.tenant, docId);
yield taskResult.restoreInitialPassword(ctx, docId);
sqlBase.deleteChanges(ctx, docId, null);
//delete forgotten after successful send on callbackUrl
yield storage.deletePath(ctx, docId, cfgForgottenFiles);
@ -1127,9 +1135,9 @@ function createSaveTimer(ctx, docId, opt_userId, opt_userIndex, opt_queue, opt_n
var updateMask = new taskResult.TaskResultData();
updateMask.tenant = ctx.tenant;
updateMask.key = docId;
updateMask.status = taskResult.FileStatus.Ok;
updateMask.status = commonDefines.FileStatus.Ok;
var updateTask = new taskResult.TaskResultData();
updateTask.status = taskResult.FileStatus.SaveVersion;
updateTask.status = commonDefines.FileStatus.SaveVersion;
updateTask.statusInfo = utils.getMillisecondsOfHour(new Date());
var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask);
if (updateIfRes.affectedRows > 0) {
@ -1308,9 +1316,34 @@ function encryptPasswordParams(ctx, data) {
}
exports.encryptPasswordParams = encryptPasswordParams;
exports.install = function(server, callbackFunction) {
var sockjs_echo = sockjs.createServer(cfgSockjs);
const io = new Server(server, cfgSocketIoConnection);
sockjs_echo.on('connection', function(conn) {
io.use((socket, next) => {
co(function*(){
let ctx = new operationContext.Context();
let res;
let checkJwtRes;
try {
ctx.initFromConnection(socket);
ctx.logger.info('io.use start');
let handshake = socket.handshake;
if (cfgTokenEnableBrowser) {
checkJwtRes = yield checkJwt(ctx, handshake?.auth?.token, commonDefines.c_oAscSecretType.Browser);
if (!checkJwtRes.decoded) {
res = new Error("not authorized");
res.data = { code: checkJwtRes.code, description: checkJwtRes.description };
}
}
} catch (err) {
ctx.logger.error('io.use error: %s', err.stack);
} finally {
ctx.logger.info('io.use end');
next(res);
}
});
});
io.on('connection', function(conn) {
if (!conn) {
operationContext.global.logger.error("null == conn");
return;
@ -1325,7 +1358,7 @@ exports.install = function(server, callbackFunction) {
conn.sessionIsSendWarning = false;
conn.sessionTimeConnect = conn.sessionTimeLastAction = new Date().getTime();
conn.on('data', function(message) {
conn.on('message', function(data) {
return co(function* () {
var docId = 'null';
let ctx = new operationContext.Context();
@ -1336,7 +1369,6 @@ exports.install = function(server, callbackFunction) {
startDate = new Date();
}
var data = JSON.parse(message);
docId = conn.docId;
ctx.logger.info('data.type = %s', data.type);
if(getIsShutdown())
@ -1347,13 +1379,15 @@ exports.install = function(server, callbackFunction) {
if (conn.isCiriticalError && ('message' == data.type || 'getLock' == data.type || 'saveChanges' == data.type ||
'isSaveLock' == data.type)) {
ctx.logger.warn("conn.isCiriticalError send command: type = %s", data.type);
conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
conn.disconnect(true);
return;
}
if ((conn.isCloseCoAuthoring || (conn.user && conn.user.view)) &&
('getLock' == data.type || 'saveChanges' == data.type || 'isSaveLock' == data.type)) {
ctx.logger.warn("conn.user.view||isCloseCoAuthoring access deny: type = %s", data.type);
conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
conn.disconnect(true);
return;
}
yield encryptPasswordParams(ctx, data);
@ -1363,7 +1397,8 @@ exports.install = function(server, callbackFunction) {
yield* auth(ctx, conn, data);
} catch(err){
ctx.logger.error('auth error: %s', err.stack);
conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
conn.disconnect(true);
return;
}
break;
@ -1392,7 +1427,7 @@ exports.install = function(server, callbackFunction) {
yield* checkEndAuthLock(ctx, data.unlock, data.isSave, docId, conn.user.id, data.releaseLocks, data.deleteIndex, conn);
break;
case 'close':
yield* closeDocument(ctx, conn, false);
yield* closeDocument(ctx, conn);
break;
case 'versionHistory' : {
let cmd = new commonDefines.InputCommand(data.cmd);
@ -1405,9 +1440,12 @@ exports.install = function(server, callbackFunction) {
yield canvasService.openDocument(ctx, conn, cmd);
break;
}
case 'changesError':
ctx.logger.error("changesError: %s", data.stack);
if (cfgErrorFiles && docId) {
case 'clientLog':
let level = data.level?.toLowerCase();
if("trace" === level || "debug" === level || "info" === level || "warn" === level || "error" === level || "fatal" === level) {
ctx.logger[level]("clientLog: %s", data.msg);
}
if ("error" === level && cfgErrorFiles && docId) {
let destDir = 'browser/' + docId;
yield storage.copyPath(ctx, docId, destDir, undefined, cfgErrorFiles);
yield* saveErrorChanges(ctx, docId, destDir);
@ -1417,12 +1455,6 @@ exports.install = function(server, callbackFunction) {
conn.sessionIsSendWarning = false;
conn.sessionTimeLastAction = new Date().getTime() - data.idletime;
break;
case 'clientLog':
let level = data.level?.toLowerCase();
if("trace" === level || "debug" === level || "info" === level || "warn" === level || "error" === level || "fatal" === level) {
ctx.logger[level]("clientLog: %s", data.msg);
}
break;
case 'forceSaveStart' :
var forceSaveRes;
if (conn.user) {
@ -1439,7 +1471,7 @@ exports.install = function(server, callbackFunction) {
delete conn.authChangesAck;
break;
default:
ctx.logger.debug("unknown command %s", message);
ctx.logger.debug("unknown command %j", data);
break;
}
if(clientStatsD) {
@ -1452,17 +1484,12 @@ exports.install = function(server, callbackFunction) {
}
});
});
conn.on('error', function() {
let ctx = new operationContext.Context();
ctx.initFromConnection(conn);
ctx.logger.error("On error");
});
conn.on('close', function() {
conn.on("disconnect", function(reason) {
return co(function* () {
let ctx = new operationContext.Context();
try {
ctx.initFromConnection(conn);
yield* closeDocument(ctx, conn, true);
yield* closeDocument(ctx, conn, reason);
} catch (err) {
ctx.logger.error('Error conn close: %s', err.stack);
}
@ -1471,12 +1498,19 @@ exports.install = function(server, callbackFunction) {
_checkLicense(ctx, conn);
});
io.engine.on("connection_error", (err) => {
operationContext.global.logger.warn(err.req); // the request object
operationContext.global.logger.warn(err.code); // the error code, for example 1
operationContext.global.logger.warn(err.message); // the error message, for example "Session ID unknown"
operationContext.global.logger.warn(err.context); // some additional error context
});
/**
*
* @param ctx
* @param conn
* @param isCloseConnection - закрываем ли мы окончательно соединение
* @param reason - the reason of the disconnection (either client or server-side)
*/
function* closeDocument(ctx, conn, isCloseConnection) {
function* closeDocument(ctx, conn, reason) {
var userLocks, reconnected = false, bHasEditors, bHasChanges;
var docId = conn.docId;
if (null == docId) {
@ -1486,9 +1520,9 @@ exports.install = function(server, callbackFunction) {
let participantsTimestamp;
var tmpUser = conn.user;
var isView = tmpUser.view;
ctx.logger.info("Connection closed or timed out: isCloseConnection = %s", isCloseConnection);
ctx.logger.info("Connection closed or timed out: reason = %s", reason);
var isCloseCoAuthoringTmp = conn.isCloseCoAuthoring;
if (isCloseConnection) {
if (reason) {
//Notify that participant has gone
connections = _.reject(connections, function(el) {
return el.id === conn.id;//Delete this connection
@ -1546,7 +1580,7 @@ exports.install = function(server, callbackFunction) {
let selectRes = yield taskResult.select(ctx, docId);
if (selectRes.length > 0) {
var row = selectRes[0];
if (taskResult.FileStatus.UpdateVersion === row.status) {
if (commonDefines.FileStatus.UpdateVersion === row.status) {
needSendStatus = false;
}
}
@ -1770,7 +1804,7 @@ exports.install = function(server, callbackFunction) {
return el.sessionId === sessionId;//Delete this connection
});
//closing could happen during async action
if (constants.CONN_CLOSED !== conn.readyState) {
if (constants.CONN_CLOSED !== conn.conn.readyState) {
// Кладем в массив, т.к. нам нужно отправлять данные для открытия/сохранения документа
connections.push(conn);
yield addPresence(ctx, conn, true);
@ -2151,6 +2185,7 @@ exports.install = function(server, callbackFunction) {
function* auth(ctx, conn, data) {
//TODO: Do authorization etc. check md5 or query db
ctx.logger.debug('auth time: %d', data.time);
if (data.token && data.user) {
ctx.setUserId(data.user.id);
let licenseInfo = yield tenantManager.getTenantLicense(ctx);
@ -2174,7 +2209,8 @@ exports.install = function(server, callbackFunction) {
fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data);
} else if (cfgTokenRequiredParams) {
ctx.logger.error("auth missing required parameter %s (since 7.1 version)", validationErr);
conn.close(constants.JWT_ERROR_CODE, constants.JWT_ERROR_REASON);
sendDataDisconnectReason(ctx, conn, constants.JWT_ERROR_CODE, constants.JWT_ERROR_REASON);
conn.disconnect(true);
return;
} else {
ctx.logger.warn("auth missing required parameter %s (since 7.1 version)", validationErr);
@ -2183,11 +2219,13 @@ exports.install = function(server, callbackFunction) {
}
if(!fillDataFromJwtRes) {
ctx.logger.warn("fillDataFromJwt return false");
conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON);
conn.disconnect(true);
return;
}
} else {
conn.close(checkJwtRes.code, checkJwtRes.description);
sendDataDisconnectReason(ctx, conn, checkJwtRes.code, checkJwtRes.description);
conn.disconnect(true);
return;
}
}
@ -2216,7 +2254,8 @@ exports.install = function(server, callbackFunction) {
let filterStatus = yield* utils.checkHostFilter(ctx, documentCallback.hostname);
if (0 !== filterStatus) {
ctx.logger.warn('checkIpFilter error: url = %s', data.documentCallbackUrl);
conn.close(constants.DROP_CODE, constants.DROP_REASON);
sendDataDisconnectReason(ctx, conn, constants.DROP_CODE, constants.DROP_REASON);
conn.disconnect(true);
return;
}
}
@ -2242,7 +2281,7 @@ exports.install = function(server, callbackFunction) {
}
}
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return;
}
@ -2304,7 +2343,7 @@ exports.install = function(server, callbackFunction) {
return el.sessionId === data.sessionId;//Delete this connection
});
//closing could happen during async action
if (constants.CONN_CLOSED !== conn.readyState) {
if (constants.CONN_CLOSED !== conn.conn.readyState) {
// Кладем в массив, т.к. нам нужно отправлять данные для открытия/сохранения документа
connections.push(conn);
yield addPresence(ctx, conn, true);
@ -2324,8 +2363,8 @@ exports.install = function(server, callbackFunction) {
cmd.setWopiParams(wopiParams);
if (wopiParams) {
documentCallback = null;
if (!wopiParams.userAuth) {
yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Wopi without userAuth');
if (!wopiParams.userAuth || !wopiParams.commonInfo) {
yield* sendFileErrorAuth(ctx, conn, data.sessionId, `invalid wopi callback (maybe postgres<9.5) ${JSON.stringify(wopiParams)}`);
return;
}
}
@ -2338,16 +2377,16 @@ exports.install = function(server, callbackFunction) {
}
if (!conn.user.view) {
var status = result && result.length > 0 ? result[0]['status'] : null;
if (taskResult.FileStatus.Ok === status) {
if (commonDefines.FileStatus.Ok === status) {
// Все хорошо, статус обновлять не нужно
} else if (taskResult.FileStatus.SaveVersion === status ||
(!bIsRestore && taskResult.FileStatus.UpdateVersion === status &&
} else if (commonDefines.FileStatus.SaveVersion === status ||
(!bIsRestore && commonDefines.FileStatus.UpdateVersion === status &&
Date.now() - result[0]['status_info'] * 60000 > cfgExpUpdateVersionStatus)) {
let newStatus = taskResult.FileStatus.Ok;
if (taskResult.FileStatus.UpdateVersion === status) {
let newStatus = commonDefines.FileStatus.Ok;
if (commonDefines.FileStatus.UpdateVersion === status) {
ctx.logger.warn("UpdateVersion expired");
//FileStatus.None to open file again from new url
newStatus = taskResult.FileStatus.None;
newStatus = commonDefines.FileStatus.None;
}
// Обновим статус файла (идет сборка, нужно ее остановить)
var updateMask = new taskResult.TaskResultData();
@ -2364,11 +2403,11 @@ exports.install = function(server, callbackFunction) {
yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE);
return;
}
} else if (bIsRestore && taskResult.FileStatus.UpdateVersion === status) {
} else if (bIsRestore && commonDefines.FileStatus.UpdateVersion === status) {
// error version
yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE);
return;
} else if (taskResult.FileStatus.None === status && conn.encrypted) {
} else if (commonDefines.FileStatus.None === status && conn.encrypted) {
//ok
} else if (bIsRestore) {
// Other error
@ -2435,7 +2474,7 @@ exports.install = function(server, callbackFunction) {
const docId = conn.docId;
const tmpUser = conn.user;
let hasForgotten;
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
@ -2453,7 +2492,7 @@ exports.install = function(server, callbackFunction) {
}
}
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
@ -2470,7 +2509,7 @@ exports.install = function(server, callbackFunction) {
}
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
@ -2479,7 +2518,7 @@ exports.install = function(server, callbackFunction) {
if (!bIsRestore && 2 === countNoView && !tmpUser.view) {
// Ставим lock на документ
const lockRes = yield editorData.lockAuth(ctx, docId, firstParticipantNoView.id, 2 * cfgExpLockDoc);
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
@ -2493,7 +2532,7 @@ exports.install = function(server, callbackFunction) {
yield* setLockDocumentTimer(ctx, docId, lockDocument.id);
}
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
@ -2508,13 +2547,13 @@ exports.install = function(server, callbackFunction) {
if (!bIsRestore && needSendChanges(conn)) {
yield* sendAuthChanges(ctx, conn.docId, [conn]);
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
yield* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, hasForgotten, opt_openedAt);
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
//closing could happen during async action
return false;
}
@ -2530,14 +2569,21 @@ exports.install = function(server, callbackFunction) {
do {
changes = yield sqlBase.getChangesPromise(ctx, docId, index, index + cfgMaxRequestChanges);
if (changes.length > 0) {
let changesJSON = indexChunk > 1 ? ',[' : '[';
changesJSON += changes[0].change_data;
for (let i = 1; i < changes.length; ++i) {
changesJSON += ',';
changesJSON += changes[i].change_data;
let buffer;
if (cfgEditor['binaryChanges']) {
let buffers = changes.map(elem => elem.change_data);
buffers.unshift(Buffer.from(utils.getChangesFileHeader(), 'utf-8'))
buffer = Buffer.concat(buffers);
} else {
let changesJSON = indexChunk > 1 ? ',[' : '[';
changesJSON += changes[0].change_data;
for (let i = 1; i < changes.length; ++i) {
changesJSON += ',';
changesJSON += changes[i].change_data;
}
changesJSON += ']\r\n';
buffer = Buffer.from(changesJSON, 'utf8');
}
changesJSON += ']\r\n';
let buffer = Buffer.from(changesJSON, 'utf8');
yield storage.putObject(ctx, changesPrefix + (indexChunk++).toString().padStart(3, '0'), buffer, buffer.length, cfgErrorFiles);
}
index += cfgMaxRequestChanges;
@ -2797,7 +2843,7 @@ exports.install = function(server, callbackFunction) {
// Стартовый индекс изменения при добавлении
const startIndex = puckerIndex;
const newChanges = JSON.parse(data.changes);
const newChanges = cfgEditor['binaryChanges'] ? data.changes : JSON.parse(data.changes);
let newChangesLastDate = new Date();
newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding
let newChangesLastTime = newChangesLastDate.getTime();
@ -2808,7 +2854,8 @@ exports.install = function(server, callbackFunction) {
for (let i = 0; i < newChanges.length; ++i) {
oElement = newChanges[i];
arrNewDocumentChanges.push({docid: docId, change: JSON.stringify(oElement), time: newChangesLastDate,
let change = cfgEditor['binaryChanges'] ? oElement : JSON.stringify(oElement);
arrNewDocumentChanges.push({docid: docId, change: change, time: newChangesLastDate,
user: userId, useridoriginal: conn.user.idOriginal});
}
@ -3036,7 +3083,8 @@ exports.install = function(server, callbackFunction) {
let rights = constants.RIGHTS.Edit;
if (config.get('server.edit_singleton')) {
// ToDo docId from url ?
const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(conn.url);
let handshake = conn.handshake;
const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(handshake.url);
if (docIdParsed && 1 < docIdParsed.length) {
const participantsMap = yield getParticipantMap(ctx, docIdParsed[1]);
for (let i = 0; i < participantsMap.length; ++i) {
@ -3151,11 +3199,6 @@ exports.install = function(server, callbackFunction) {
return licenseType;
}
sockjs_echo.installHandlers(server, {prefix: '/doc/['+constants.DOC_ID_PATTERN+']*/c', log: function(severity, message) {
//TODO: handle severity
operationContext.global.logger.info(message);
}});
//publish subscribe message brocker
function pubsubOnMessage(msg) {
return co(function* () {
@ -3176,7 +3219,7 @@ exports.install = function(server, callbackFunction) {
}
break;
case commonDefines.c_oPublishType.closeConnection:
closeUsersConnection(data.docId, data.usersMap, data.isOriginalId, data.code, data.description);
closeUsersConnection(ctx, data.docId, data.usersMap, data.isOriginalId, data.code, data.description);
break;
case commonDefines.c_oPublishType.releaseLock:
participants = getParticipants(data.docId, true, data.userId, true);
@ -3402,11 +3445,12 @@ exports.install = function(server, callbackFunction) {
});
} else if (nowMs - conn.sessionTimeConnect > cfgExpSessionAbsolute) {
ctx.logger.debug('expireDoc close absolute session');
conn.close(constants.SESSION_ABSOLUTE_CODE, constants.SESSION_ABSOLUTE_REASON);
sendDataDisconnectReason(ctx, conn, constants.SESSION_ABSOLUTE_CODE, constants.SESSION_ABSOLUTE_REASON);
conn.disconnect(true);
continue;
}
}
if (cfgExpSessionIdle > 0 && !conn.user?.view) {
if (cfgExpSessionIdle > 0 && !(conn.user?.view || conn.isCloseCoAuthoring)) {
if (maxMs - conn.sessionTimeLastAction > cfgExpSessionIdle && !conn.sessionIsSendWarning) {
conn.sessionIsSendWarning = true;
sendDataSession(ctx, conn, {
@ -3416,11 +3460,12 @@ exports.install = function(server, callbackFunction) {
});
} else if (nowMs - conn.sessionTimeLastAction > cfgExpSessionIdle) {
ctx.logger.debug('expireDoc close idle session');
conn.close(constants.SESSION_IDLE_CODE, constants.SESSION_IDLE_REASON);
sendDataDisconnectReason(ctx, conn, constants.SESSION_IDLE_CODE, constants.SESSION_IDLE_REASON);
conn.disconnect(true);
continue;
}
}
if (constants.CONN_CLOSED === conn.readyState) {
if (constants.CONN_CLOSED === conn.conn.readyState) {
ctx.logger.error('expireDoc connection closed');
}
yield addPresence(ctx, conn, false);
@ -3482,7 +3527,7 @@ exports.install = function(server, callbackFunction) {
let callback = selectRes[0].callback;
let callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, callback);
let wopiParams = wopiClient.parseWopiCallback(ctx, callbackUrl, callback);
if (wopiParams) {
if (wopiParams && wopiParams.commonInfo) {
yield wopiClient.lock(ctx, 'REFRESH_LOCK', wopiParams.commonInfo.lockId,
wopiParams.commonInfo.fileInfo, wopiParams.userAuth);
}
@ -3515,7 +3560,7 @@ exports.install = function(server, callbackFunction) {
}
gc.startGC();
let tableName = 'task_result';
let tableName = cfgTableResult;
const tableRequiredColumn = 'tenant';
//check data base compatibility
sqlBase.getTableColumns(operationContext.global, tableName).then(function(res) {
@ -3643,28 +3688,33 @@ exports.licenseInfo = function(req, res) {
var precisionIndex = 0;
for (let i = redisRes.length - 1; i >= 0; i--) {
let elem = redisRes[i];
//skip duplicates in cluster
if (prevTime - elem.time >= expDocumentsStep95) {
for (let j = precisionIndex; j < PRECISION.length; ++j) {
if (now - elem.time < PRECISION[j].val) {
let precision = precisionSum[PRECISION[j].name];
precision.edit.min = Math.min(precision.edit.min, elem.edit);
precision.edit.max = Math.max(precision.edit.max, elem.edit);
precision.edit.sum += elem.edit;
precision.edit.count++;
precision.view.min = Math.min(precision.view.min, elem.view);
precision.view.max = Math.max(precision.view.max, elem.view);
precision.view.sum += elem.view;
precision.view.count++;
if (elem.liveview) {
precision.liveview.min = Math.min(precision.liveview.min, elem.liveview);
precision.liveview.max = Math.max(precision.liveview.max, elem.liveview);
precision.liveview.sum += elem.liveview;
precision.liveview.count++;
}
} else {
precisionIndex = j + 1;
}
let edit = elem.edit || 0;
let view = elem.view || 0;
let liveview = elem.liveview || 0;
//for cluster
while (i > 0 && elem.time - redisRes[i - 1].time < expDocumentsStep95) {
edit += elem.edit || 0;
view += elem.view || 0;
liveview += elem.liveview || 0;
i--;
}
for (let j = precisionIndex; j < PRECISION.length; ++j) {
if (now - elem.time < PRECISION[j].val) {
let precision = precisionSum[PRECISION[j].name];
precision.edit.min = Math.min(precision.edit.min, edit);
precision.edit.max = Math.max(precision.edit.max, edit);
precision.edit.sum += edit
precision.edit.count++;
precision.view.min = Math.min(precision.view.min, view);
precision.view.max = Math.max(precision.view.max, view);
precision.view.sum += view;
precision.view.count++;
precision.liveview.min = Math.min(precision.liveview.min, liveview);
precision.liveview.max = Math.max(precision.liveview.max, liveview);
precision.liveview.sum += liveview;
precision.liveview.count++;
} else {
precisionIndex = j + 1;
}
}
prevTime = elem.time;

View File

@ -38,17 +38,27 @@ var sqlDataBaseType = {
postgreSql : 'postgres'
};
var config = require('config').get('services.CoAuthoring.sql');
var baseConnector = (sqlDataBaseType.mySql === config.get('type') || sqlDataBaseType.mariaDB === config.get('type')) ? require('./mySqlBaseConnector') : require('./postgreSqlBaseConnector');
var bottleneck = require("bottleneck");
var config = require('config');
var configSql = config.get('services.CoAuthoring.sql');
var baseConnector = (sqlDataBaseType.mySql === configSql.get('type') || sqlDataBaseType.mariaDB === configSql.get('type')) ? require('./mySqlBaseConnector') : require('./postgreSqlBaseConnector');
let constants = require('./../../Common/sources/constants');
const tableChanges = config.get('tableChanges'),
tableResult = config.get('tableResult');
const cfgTableResult = configSql.get('tableResult');
const cfgTableChanges = configSql.get('tableChanges');
var g_oCriticalSection = {};
let isSupportFastInsert = !!baseConnector.insertChanges;
let addSqlParam = baseConnector.addSqlParameter;
var maxPacketSize = config.get('max_allowed_packet'); // Размер по умолчанию для запроса в базу данных 1Mb - 1 (т.к. он не пишет 1048575, а пишет 1048574)
var maxPacketSize = configSql.get('max_allowed_packet'); // Размер по умолчанию для запроса в базу данных 1Mb - 1 (т.к. он не пишет 1048575, а пишет 1048574)
const cfgBottleneckGetChanges = config.get('bottleneck.getChanges');
let reservoirMaximum = cfgBottleneckGetChanges.reservoirIncreaseMaximum || cfgBottleneckGetChanges.reservoirRefreshAmount;
let group = new bottleneck.Group(cfgBottleneckGetChanges);
function getChangesSize(changes) {
return changes.reduce((accumulator, currentValue) => accumulator + currentValue.change_data.length, 0);
}
exports.baseConnector = baseConnector;
exports.insertChangesPromiseCompatibility = function (ctx, objChanges, docId, index, user) {
@ -64,7 +74,7 @@ exports.insertChangesPromiseCompatibility = function (ctx, objChanges, docId, in
};
exports.insertChangesPromiseFast = function (ctx, objChanges, docId, index, user) {
return new Promise(function(resolve, reject) {
baseConnector.insertChanges(ctx, tableChanges, 0, objChanges, docId, index, user, function(error, result, isSupported) {
baseConnector.insertChanges(ctx, cfgTableChanges, 0, objChanges, docId, index, user, function(error, result, isSupported) {
isSupportFastInsert = isSupported;
if (error) {
if (!isSupportFastInsert) {
@ -93,7 +103,7 @@ function _getDateTime2(oDate) {
exports.getDateTime = _getDateTime2;
function _insertChangesCallback (ctx, startIndex, objChanges, docId, index, user, callback) {
var sqlCommand = `INSERT INTO ${tableChanges} VALUES`;
var sqlCommand = `INSERT INTO ${cfgTableChanges} VALUES`;
var i = startIndex, l = objChanges.length, lengthUtf8Current = sqlCommand.length, lengthUtf8Row = 0, values = [];
if (i === l)
return;
@ -136,9 +146,9 @@ exports.deleteChangesCallback = function(ctx, docId, deleteIndex, callback) {
let p2 = addSqlParam(docId, values);
if (null !== deleteIndex) {
let sqlParam2 = addSqlParam(deleteIndex, values);
sqlCommand = `DELETE FROM ${tableChanges} WHERE tenant=${p1} AND id=${p2} AND change_id >= ${sqlParam2};`;
sqlCommand = `DELETE FROM ${cfgTableChanges} WHERE tenant=${p1} AND id=${p2} AND change_id >= ${sqlParam2};`;
} else {
sqlCommand = `DELETE FROM ${tableChanges} WHERE tenant=${p1} AND id=${p2};`;
sqlCommand = `DELETE FROM ${cfgTableChanges} WHERE tenant=${p1} AND id=${p2};`;
}
baseConnector.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values);
};
@ -163,7 +173,7 @@ exports.getChangesIndex = function(ctx, docId, callback) {
let values = [];
let p1 = addSqlParam(ctx.tenant, values);
let p2 = addSqlParam(docId, values);
var sqlCommand = `SELECT MAX(change_id) as change_id FROM ${tableChanges} WHERE tenant=${p1} AND id=${p2};`;
var sqlCommand = `SELECT MAX(change_id) as change_id FROM ${cfgTableChanges} WHERE tenant=${p1} AND id=${p2};`;
baseConnector.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values);
};
exports.getChangesIndexPromise = function(ctx, docId) {
@ -178,37 +188,48 @@ exports.getChangesIndexPromise = function(ctx, docId) {
});
};
exports.getChangesPromise = function (ctx, docId, optStartIndex, optEndIndex, opt_time) {
return new Promise(function(resolve, reject) {
let values = [];
let sqlParam = addSqlParam(ctx.tenant, values);
let sqlWhere = `tenant=${sqlParam}`;
sqlParam = addSqlParam(docId, values);
sqlWhere += ` AND id=${sqlParam}`;
if (null != optStartIndex) {
sqlParam = addSqlParam(optStartIndex, values);
sqlWhere += ` AND change_id>=${sqlParam}`;
}
if (null != optEndIndex) {
sqlParam = addSqlParam(optEndIndex, values);
sqlWhere += ` AND change_id<${sqlParam}`;
}
if (null != opt_time) {
if (!(opt_time instanceof Date)) {
opt_time = new Date(opt_time);
let limiter = group.key(`${ctx.tenant}\t${docId}\tchanges`);
return limiter.schedule(() => {
return new Promise(function(resolve, reject) {
let values = [];
let sqlParam = addSqlParam(ctx.tenant, values);
let sqlWhere = `tenant=${sqlParam}`;
sqlParam = addSqlParam(docId, values);
sqlWhere += ` AND id=${sqlParam}`;
if (null != optStartIndex) {
sqlParam = addSqlParam(optStartIndex, values);
sqlWhere += ` AND change_id>=${sqlParam}`;
}
sqlParam = addSqlParam(opt_time, values);
sqlWhere += ` AND change_date<=${sqlParam}`;
}
sqlWhere += ' ORDER BY change_id ASC';
var sqlCommand = `SELECT * FROM ${tableChanges} WHERE ${sqlWhere};`;
if (null != optEndIndex) {
sqlParam = addSqlParam(optEndIndex, values);
sqlWhere += ` AND change_id<${sqlParam}`;
}
if (null != opt_time) {
if (!(opt_time instanceof Date)) {
opt_time = new Date(opt_time);
}
sqlParam = addSqlParam(opt_time, values);
sqlWhere += ` AND change_date<=${sqlParam}`;
}
sqlWhere += ' ORDER BY change_id ASC';
var sqlCommand = `SELECT * FROM ${cfgTableChanges} WHERE ${sqlWhere};`;
baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
} else {
resolve(result);
}
}, undefined, undefined, values);
baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
} else {
if (reservoirMaximum > 0) {
let size = Math.min(getChangesSize(result), reservoirMaximum);
let cur = limiter.incrementReservoir(-size).then((cur) => {
ctx.logger.debug("getChangesPromise bottleneck reservoir cur=%s", cur);
resolve(result);
});
} else {
resolve(result);
}
}
}, undefined, undefined, values);
});
});
};
@ -252,7 +273,7 @@ exports.healthCheck = function (ctx) {
exports.getEmptyCallbacks = function(ctx) {
return new Promise(function(resolve, reject) {
const sqlCommand = "SELECT DISTINCT t1.tenant, t1.id FROM doc_changes t1 LEFT JOIN task_result t2 ON t2.tenant = t1.tenant AND t2.id = t1.id WHERE t2.callback = '';";
const sqlCommand = `SELECT DISTINCT t1.tenant, t1.id FROM ${cfgTableChanges} t1 LEFT JOIN ${cfgTableResult} t2 ON t2.tenant = t1.tenant AND t2.id = t1.id WHERE t2.callback = '';`;
baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);

View File

@ -182,18 +182,18 @@ var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAd
openedAt = getOpenedAt(row);
}
switch (status) {
case taskResult.FileStatus.SaveVersion:
case taskResult.FileStatus.UpdateVersion:
case taskResult.FileStatus.Ok:
if(taskResult.FileStatus.Ok == status) {
case commonDefines.FileStatus.SaveVersion:
case commonDefines.FileStatus.UpdateVersion:
case commonDefines.FileStatus.Ok:
if(commonDefines.FileStatus.Ok == status) {
outputData.setStatus('ok');
} else if (taskResult.FileStatus.SaveVersion == status ||
(!opt_bIsRestore && taskResult.FileStatus.UpdateVersion === status &&
} else if (commonDefines.FileStatus.SaveVersion == status ||
(!opt_bIsRestore && commonDefines.FileStatus.UpdateVersion === status &&
Date.now() - statusInfo * 60000 > cfgExpUpdateVersionStatus)) {
if (optConn && (optConn.user.view || optConn.isCloseCoAuthoring)) {
outputData.setStatus(constants.FILE_STATUS_UPDATE_VERSION);
} else {
if (taskResult.FileStatus.UpdateVersion === status) {
if (commonDefines.FileStatus.UpdateVersion === status) {
ctx.logger.warn("UpdateVersion expired");
}
var updateMask = new taskResult.TaskResultData();
@ -202,7 +202,7 @@ var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAd
updateMask.status = status;
updateMask.statusInfo = statusInfo;
var updateTask = new taskResult.TaskResultData();
updateTask.status = taskResult.FileStatus.Ok;
updateTask.status = commonDefines.FileStatus.Ok;
updateTask.statusInfo = constants.NO_ERROR;
var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask);
if (updateIfRes.affectedRows > 0) {
@ -264,7 +264,7 @@ var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAd
}
}
break;
case taskResult.FileStatus.NeedParams:
case commonDefines.FileStatus.NeedParams:
outputData.setStatus('needparams');
var settingsPath = key + '/' + 'origin.' + cmd.getFormat();
if (optConn) {
@ -276,15 +276,15 @@ var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAd
optAdditionalOutput.needUrlType = commonDefines.c_oAscUrlTypes.Temporary;
}
break;
case taskResult.FileStatus.NeedPassword:
case commonDefines.FileStatus.NeedPassword:
outputData.setStatus('needpassword');
outputData.setData(statusInfo);
break;
case taskResult.FileStatus.Err:
case taskResult.FileStatus.ErrToReload:
case commonDefines.FileStatus.Err:
case commonDefines.FileStatus.ErrToReload:
outputData.setStatus('err');
outputData.setData(statusInfo);
if (taskResult.FileStatus.ErrToReload == status) {
if (commonDefines.FileStatus.ErrToReload == status) {
let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback);
let wopiParams = wopiClient.parseWopiCallback(ctx, userAuthStr);
if (!wopiParams) {
@ -293,10 +293,10 @@ var getOutputData = co.wrap(function* (ctx, cmd, outputData, key, optConn, optAd
}
}
break;
case taskResult.FileStatus.None:
case commonDefines.FileStatus.None:
outputData.setStatus('none');
break;
case taskResult.FileStatus.WaitQueue:
case commonDefines.FileStatus.WaitQueue:
//task in the queue. response will be after convertion
break;
default:
@ -378,7 +378,7 @@ function* getUpdateResponse(ctx, cmd) {
updateTask.key = cmd.getSaveKey() ? cmd.getSaveKey() : cmd.getDocId();
var statusInfo = cmd.getStatusInfo();
if (constants.NO_ERROR == statusInfo) {
updateTask.status = taskResult.FileStatus.Ok;
updateTask.status = commonDefines.FileStatus.Ok;
let password = cmd.getPassword();
if (password) {
if (false === hasPasswordCol) {
@ -390,21 +390,21 @@ function* getUpdateResponse(ctx, cmd) {
}
}
} else if (constants.CONVERT_DOWNLOAD == statusInfo) {
updateTask.status = taskResult.FileStatus.ErrToReload;
updateTask.status = commonDefines.FileStatus.ErrToReload;
} else if (constants.CONVERT_NEED_PARAMS == statusInfo) {
updateTask.status = taskResult.FileStatus.NeedParams;
updateTask.status = commonDefines.FileStatus.NeedParams;
} else if (constants.CONVERT_DRM == statusInfo || constants.CONVERT_PASSWORD == statusInfo) {
if (cfgOpenProtectedFile) {
updateTask.status = taskResult.FileStatus.NeedPassword;
updateTask.status = commonDefines.FileStatus.NeedPassword;
} else {
updateTask.status = taskResult.FileStatus.Err;
updateTask.status = commonDefines.FileStatus.Err;
}
} else if (constants.CONVERT_DRM_UNSUPPORTED == statusInfo) {
updateTask.status = taskResult.FileStatus.Err;
updateTask.status = commonDefines.FileStatus.Err;
} else if (constants.CONVERT_DEAD_LETTER == statusInfo) {
updateTask.status = taskResult.FileStatus.ErrToReload;
updateTask.status = commonDefines.FileStatus.ErrToReload;
} else {
updateTask.status = taskResult.FileStatus.Err;
updateTask.status = commonDefines.FileStatus.Err;
}
updateTask.statusInfo = statusInfo;
return updateTask;
@ -443,7 +443,7 @@ function commandOpenStartPromise(ctx, docId, baseUrl, opt_updateUserIndex, opt_d
task.tenant = ctx.tenant;
task.key = docId;
//None instead WaitQueue to prevent: conversion task is lost when entering and leaving the editor quickly(that leads to an endless opening)
task.status = taskResult.FileStatus.None;
task.status = commonDefines.FileStatus.None;
task.statusInfo = constants.NO_ERROR;
task.baseurl = baseUrl;
if (opt_documentCallbackUrl) {
@ -478,10 +478,10 @@ function* commandOpen(ctx, conn, cmd, outputData, opt_upsertRes, opt_bIsRestore)
let updateMask = new taskResult.TaskResultData();
updateMask.tenant = ctx.tenant;
updateMask.key = cmd.getDocId();
updateMask.status = taskResult.FileStatus.None;
updateMask.status = commonDefines.FileStatus.None;
let task = new taskResult.TaskResultData();
task.status = taskResult.FileStatus.WaitQueue;
task.status = commonDefines.FileStatus.WaitQueue;
task.statusInfo = constants.NO_ERROR;
let updateIfRes = yield taskResult.updateIf(ctx, task, updateMask);
@ -537,10 +537,10 @@ function* commandReopen(ctx, conn, cmd, outputData) {
let updateMask = new taskResult.TaskResultData();
updateMask.tenant = ctx.tenant;
updateMask.key = cmd.getDocId();
updateMask.status = isPassword ? taskResult.FileStatus.NeedPassword : taskResult.FileStatus.NeedParams;
updateMask.status = isPassword ? commonDefines.FileStatus.NeedPassword : commonDefines.FileStatus.NeedParams;
var task = new taskResult.TaskResultData();
task.status = taskResult.FileStatus.WaitQueue;
task.status = commonDefines.FileStatus.WaitQueue;
task.statusInfo = constants.NO_ERROR;
var upsertRes = yield taskResult.updateIf(ctx, task, updateMask);
@ -617,7 +617,7 @@ let commandSfctByCmd = co.wrap(function*(ctx, cmd, opt_priority, opt_expiration,
yield* addRandomKeyTaskCmd(ctx, cmd);
addPasswordToCmd(ctx, cmd, row.password);
let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback);
cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr));
cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback));
cmd.setOutputFormat(changeFormatByOrigin(ctx, row, cmd.getOutputFormat()));
cmd.setJsonParams(getOpenedAtJSONParams(row));
var queueData = getSaveTask(ctx, cmd);
@ -821,7 +821,7 @@ function* commandSetPassword(ctx, conn, cmd, outputData) {
if (selectRes.length > 0) {
let row = selectRes[0];
hasPasswordCol = undefined !== row.password;
if (taskResult.FileStatus.Ok === row.status && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password)) {
if (commonDefines.FileStatus.Ok === row.status && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password)) {
hasDocumentPassword = true;
}
}
@ -830,7 +830,7 @@ function* commandSetPassword(ctx, conn, cmd, outputData) {
let updateMask = new taskResult.TaskResultData();
updateMask.tenant = ctx.tenant;
updateMask.key = cmd.getDocId();
updateMask.status = taskResult.FileStatus.Ok;
updateMask.status = commonDefines.FileStatus.Ok;
let newChangesLastDate = new Date();
newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding
@ -924,10 +924,10 @@ function* commandSfcCallback(ctx, cmd, isSfcm, isEncrypted) {
statusErr = docsCoServer.c_oAscServerStatus.Corrupted;
}
let recoverTask = new taskResult.TaskResultData();
recoverTask.status = taskResult.FileStatus.Ok;
recoverTask.status = commonDefines.FileStatus.Ok;
recoverTask.statusInfo = constants.NO_ERROR;
let updateIfTask = new taskResult.TaskResultData();
updateIfTask.status = taskResult.FileStatus.UpdateVersion;
updateIfTask.status = commonDefines.FileStatus.UpdateVersion;
updateIfTask.statusInfo = Math.floor(Date.now() / 60000);//minutes
let updateIfRes;
@ -938,12 +938,12 @@ function* commandSfcCallback(ctx, cmd, isSfcm, isEncrypted) {
if (isEncrypted) {
recoverTask.status = updateMask.status = row.status;
recoverTask.statusInfo = updateMask.statusInfo = row.status_info;
} else if ((taskResult.FileStatus.SaveVersion === row.status && cmd.getStatusInfoIn() === row.status_info) ||
taskResult.FileStatus.UpdateVersion === row.status) {
if (taskResult.FileStatus.UpdateVersion === row.status) {
} else if ((commonDefines.FileStatus.SaveVersion === row.status && cmd.getStatusInfoIn() === row.status_info) ||
commonDefines.FileStatus.UpdateVersion === row.status) {
if (commonDefines.FileStatus.UpdateVersion === row.status) {
updateIfRes = {affectedRows: 1};
}
recoverTask.status = taskResult.FileStatus.SaveVersion;
recoverTask.status = commonDefines.FileStatus.SaveVersion;
recoverTask.statusInfo = cmd.getStatusInfoIn();
updateMask.status = row.status;
updateMask.statusInfo = row.status_info;
@ -1017,7 +1017,7 @@ function* commandSfcCallback(ctx, cmd, isSfcm, isEncrypted) {
let selectRes = yield taskResult.select(ctx, docId);
let row = selectRes.length > 0 ? selectRes[0] : null;
//send only if FileStatus.Ok to prevent forcesave after final save
if (row && row.status == taskResult.FileStatus.Ok) {
if (row && row.status == commonDefines.FileStatus.Ok) {
if (forceSave) {
let forceSaveDate = forceSave.getTime() ? new Date(forceSave.getTime()): new Date();
outputSfc.setForceSaveType(forceSaveType);
@ -1539,6 +1539,8 @@ exports.downloadFile = function(req, res) {
url = decoded.changesUrl;
} else if (decoded.document && -1 !== cfgDownloadFileAllowExt.indexOf(decoded.document.fileType)) {
url = decoded.document.url;
} else if (decoded.url && -1 !== cfgDownloadFileAllowExt.indexOf(decoded.fileType)) {
url = decoded.url;
} else {
errorDescription = 'access deny';
}
@ -1589,7 +1591,7 @@ exports.saveFromChanges = function(ctx, docId, statusInfo, optFormat, opt_userId
//делаем select, потому что за время timeout информация могла измениться
var selectRes = yield taskResult.select(ctx, docId);
var row = selectRes.length > 0 ? selectRes[0] : null;
if (row && row.status == taskResult.FileStatus.SaveVersion && row.status_info == statusInfo) {
if (row && row.status == commonDefines.FileStatus.SaveVersion && row.status_info == statusInfo) {
if (null == optFormat) {
optFormat = changeFormatByOrigin(ctx, row, constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML);
}
@ -1602,7 +1604,7 @@ exports.saveFromChanges = function(ctx, docId, statusInfo, optFormat, opt_userId
cmd.setUserActionIndex(opt_userIndex);
cmd.setJsonParams(getOpenedAtJSONParams(row));
let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback);
cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr));
cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback));
addPasswordToCmd(ctx, cmd, row && row.password);
yield* addRandomKeyTaskCmd(ctx, cmd);
var queueData = getSaveTask(ctx, cmd);

View File

@ -36,12 +36,14 @@ const path = require('path');
var config = require('config');
var co = require('co');
const locale = require('windows-locale');
const mime = require('mime');
var taskResult = require('./taskresult');
var utils = require('./../../Common/sources/utils');
var constants = require('./../../Common/sources/constants');
var commonDefines = require('./../../Common/sources/commondefines');
var docsCoServer = require('./DocsCoServer');
var canvasService = require('./canvasservice');
var wopiClient = require('./wopiClient');
var storage = require('./../../Common/sources/storage-base');
var formatChecker = require('./../../Common/sources/formatchecker');
var statsDClient = require('./../../Common/sources/statsdclient');
@ -49,20 +51,20 @@ var storageBase = require('./../../Common/sources/storage-base');
var operationContext = require('./../../Common/sources/operationContext');
const sqlBase = require('./baseConnector');
const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
var CONVERT_ASYNC_DELAY = 1000;
var clientStatsD = statsDClient.getClient();
function* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword) {
function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_checkPassword) {
var status = new commonDefines.ConvertStatus(constants.NO_ERROR);
if (selectRes.length > 0) {
var docId = cmd.getDocId();
var row = selectRes[0];
let password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password);
switch (row.status) {
case taskResult.FileStatus.Ok:
case commonDefines.FileStatus.Ok:
if (password) {
let encryptedUserPassword = cmd.getPassword();
let isCorrectPassword;
if (encryptedUserPassword) {
let decryptedPassword = yield utils.decryptPassword(password);
@ -80,17 +82,17 @@ function* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword) {
status.end = true;
}
break;
case taskResult.FileStatus.Err:
case taskResult.FileStatus.ErrToReload:
case taskResult.FileStatus.NeedPassword:
case commonDefines.FileStatus.Err:
case commonDefines.FileStatus.ErrToReload:
case commonDefines.FileStatus.NeedPassword:
status.err = row.status_info;
if (taskResult.FileStatus.ErrToReload == row.status || taskResult.FileStatus.NeedPassword == row.status) {
if (commonDefines.FileStatus.ErrToReload == row.status || commonDefines.FileStatus.NeedPassword == row.status) {
yield canvasService.cleanupCache(ctx);
}
break;
case taskResult.FileStatus.NeedParams:
case taskResult.FileStatus.SaveVersion:
case taskResult.FileStatus.UpdateVersion:
case commonDefines.FileStatus.NeedParams:
case commonDefines.FileStatus.SaveVersion:
case commonDefines.FileStatus.UpdateVersion:
status.err = constants.UNKNOWN;
break;
}
@ -134,7 +136,7 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
let task = new taskResult.TaskResultData();
task.tenant = ctx.tenant;
task.key = docId;
task.status = taskResult.FileStatus.WaitQueue;
task.status = commonDefines.FileStatus.WaitQueue;
task.statusInfo = constants.NO_ERROR;
let upsertRes = yield taskResult.upsert(ctx, task);
@ -146,7 +148,7 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
var status;
if (!bCreate) {
selectRes = yield taskResult.select(ctx, docId);
status = yield* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword);
status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword);
} else {
var queueData = new commonDefines.TaskQueueData();
queueData.setCtx(ctx);
@ -168,7 +170,7 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
}
yield utils.sleep(CONVERT_ASYNC_DELAY);
selectRes = yield taskResult.select(ctx, docId);
status = yield* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword);
status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword);
waitTime += CONVERT_ASYNC_DELAY;
if (waitTime > utils.CONVERTION_TIMEOUT) {
status.err = constants.CONVERT_TIMEOUT;
@ -239,7 +241,8 @@ function convertRequest(req, res, isJson) {
utils.fillResponse(req, res, new commonDefines.ConvertStatus(authRes.code), isJson);
return;
}
let outputtype = params.outputtype || '';
let filetype = params.filetype || params.fileType || '';
let outputtype = params.outputtype || params.outputType || '';
let docId = 'conv_' + params.key + '_' + outputtype;
ctx.setDocId(docId);
@ -248,8 +251,8 @@ function convertRequest(req, res, isJson) {
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
return;
}
if (params.filetype && !constants.EXTENTION_REGEX.test(params.filetype)) {
ctx.logger.warn('convertRequest unexpected filetype = %s', params.filetype);
if (filetype && !constants.EXTENTION_REGEX.test(filetype)) {
ctx.logger.warn('convertRequest unexpected filetype = %s', filetype);
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
return;
}
@ -263,7 +266,7 @@ function convertRequest(req, res, isJson) {
cmd.setCommand('conv');
cmd.setUrl(params.url);
cmd.setEmbeddedFonts(false);//params.embeddedfonts'];
cmd.setFormat(params.filetype);
cmd.setFormat(filetype);
cmd.setDocId(docId);
cmd.setOutputFormat(outputFormat);
@ -395,30 +398,33 @@ function builderRequest(req, res) {
let error = authRes.code;
let urls;
let end = false;
if (error === constants.NO_ERROR &&
(params.key || params.url || (req.body && Buffer.isBuffer(req.body) && req.body.length > 0))) {
let needCreateId = !docId;
let isInBody = req.body && Buffer.isBuffer(req.body) && req.body.length > 0;
if (error === constants.NO_ERROR && (params.key || params.url || isInBody)) {
if (needCreateId) {
let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8);
docId = task.key;
ctx.setDocId(docId);
}
let cmd = new commonDefines.InputCommand();
cmd.setCommand('builder');
cmd.setIsBuilder(true);
cmd.setWithAuthorization(true);
cmd.setDocId(docId);
if (!docId) {
let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8);
docId = task.key;
cmd.setDocId(docId);
if (params.url) {
cmd.setUrl(params.url);
cmd.setFormat('docbuilder');
} else {
yield storageBase.putObject(ctx, docId + '/script.docbuilder', req.body, req.body.length);
}
if (params.url) {
cmd.setUrl(params.url);
cmd.setFormat('docbuilder');
} else if (isInBody) {
yield storageBase.putObject(ctx, docId + '/script.docbuilder', req.body, req.body.length);
}
if (needCreateId) {
let queueData = new commonDefines.TaskQueueData();
queueData.setCtx(ctx);
queueData.setCmd(cmd);
yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW);
}
let async = (typeof params.async === 'string') ? 'true' === params.async : params.async;
let status = yield* convertByCmd(ctx, cmd, async, utils.getBaseUrlByRequest(req), undefined, true);
let status = yield* convertByCmd(ctx, cmd, async, undefined, undefined, constants.QUEUE_PRIORITY_LOW);
end = status.end;
error = status.err;
if (end) {
@ -439,8 +445,212 @@ function builderRequest(req, res) {
}
});
}
function convertTo(req, res) {
return co(function*() {
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
ctx.logger.info('convert-to start');
let format = req.body['format'];
if (req.params.format) {
format = req.params.format;
}
let pdfVer = req.body['PDFVer'];
if (pdfVer && pdfVer.startsWith("PDF/A") && 'pdf' === format) {
format = 'pdfa';
}
let fullSheetPreview = req.body['FullSheetPreview'];
let lang = req.body['lang'];
let outputFormat = formatChecker.getFormatFromString(format);
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) {
ctx.logger.warn('convert-to unexpected format = %s', format);
res.sendStatus(400);
return;
}
//todo https://github.com/CollaboraOnline/online/blob/master/wsd/COOLWSD.cpp
//req.body['options']
let docId, fileTo, status, originalname;
if (req.file && req.file.originalname && req.file.buffer) {
originalname = req.file.originalname;
let filetype = path.extname(req.file.originalname).substring(1);
if (filetype && !constants.EXTENTION_REGEX.test(filetype)) {
ctx.logger.warn('convertRequest unexpected filetype = %s', filetype);
res.sendStatus(400);
return;
}
let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
docId = task.key;
ctx.setDocId(docId);
//todo stream
let buffer = req.file.buffer;
yield storageBase.putObject(ctx, docId + '/origin.' + filetype, buffer, buffer.length);
let cmd = new commonDefines.InputCommand();
cmd.setCommand('conv');
cmd.setDocId(docId);
cmd.setSaveKey(docId);
cmd.setFormat(filetype);
cmd.setOutputFormat(outputFormat);
if (lang && locale[lang.toLowerCase()]) {
cmd.setLCID(locale[lang.toLowerCase()].id);
}
if (fullSheetPreview) {
cmd.setJsonParams(JSON.stringify({'spreadsheetLayout': {
"ignorePrintArea": true,
"fitToWidth": 1,
"fitToHeight": 1
}}));
} else {
cmd.setJsonParams(JSON.stringify({'spreadsheetLayout': {
"ignorePrintArea": true,
"fitToWidth": 0,
"fitToHeight": 0,
"scale": 100
}}));
}
fileTo = constants.OUTPUT_NAME;
let outputExt = formatChecker.getStringFromFormat(outputFormat);
if (outputExt) {
fileTo += '.' + outputExt;
}
let queueData = new commonDefines.TaskQueueData();
queueData.setCtx(ctx);
queueData.setCmd(cmd);
queueData.setToFile(fileTo);
queueData.setFromOrigin(true);
yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW);
let async = false;
status = yield* convertByCmd(ctx, cmd, async, fileTo);
}
if (status && status.end && constants.NO_ERROR === status.err) {
let filename = path.basename(originalname, path.extname(originalname)) + path.extname(fileTo);
let streamObj = yield storage.createReadStream(ctx, `${docId}/${fileTo}`);
res.setHeader('Content-Disposition', utils.getContentDisposition(filename, null, constants.CONTENT_DISPOSITION_INLINE));
res.setHeader('Content-Length', streamObj.contentLength);
res.setHeader('Content-Type', mime.getType(filename));
yield utils.pipeStreams(streamObj.readStream, res, true);
} else {
ctx.logger.error('convert-to error status:%j', status);
res.sendStatus(400);
}
} catch (err) {
ctx.logger.error('convert-to error:%s', err.stack);
res.sendStatus(400);
} finally {
ctx.logger.info('convert-to end');
}
});
}
function convertAndEdit(ctx, wopiParams, filetypeFrom, filetypeTo) {
return co(function*() {
try {
ctx.logger.info('convert-and-edit start');
let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
let docId = task.key;
let outputFormat = formatChecker.getFormatFromString(filetypeTo);
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) {
ctx.logger.debug('convert-and-edit unknown outputFormat %s', filetypeTo);
return;
}
let cmd = new commonDefines.InputCommand();
cmd.setCommand('conv');
cmd.setDocId(docId);
cmd.setUrl('dummy-url');
cmd.setWopiParams(wopiParams);
cmd.setFormat(filetypeFrom);
cmd.setOutputFormat(outputFormat);
let fileTo = constants.OUTPUT_NAME;
let outputExt = formatChecker.getStringFromFormat(outputFormat);
if (outputExt) {
fileTo += '.' + outputExt;
}
let queueData = new commonDefines.TaskQueueData();
queueData.setCtx(ctx);
queueData.setCmd(cmd);
queueData.setToFile(fileTo);
yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW);
let async = true;
yield* convertByCmd(ctx, cmd, async, fileTo);
return docId;
} catch (err) {
ctx.logger.error('convert-and-edit error:%s', err.stack);
} finally {
ctx.logger.info('convert-and-edit end');
}
});
}
function getConverterHtmlHandler(req, res) {
return co(function*() {
let isJson = true;
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
ctx.logger.info('convert-and-edit-handler start');
let wopiSrc = req.query['wopisrc'];
let access_token = req.query['access_token'];
let targetext = req.query['targetext'];
let docId = req.query['docid'];
ctx.setDocId(docId);
if (!(wopiSrc && access_token && access_token && targetext && docId) ||
constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === formatChecker.getFormatFromString(targetext)) {
ctx.logger.debug('convert-and-edit-handler invalid params: wopiSrc=%s; access_token=%s; targetext=%s; docId=%s', wopiSrc, access_token, targetext, docId);
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
return;
}
let token = req.query['token'];
if (cfgTokenEnableBrowser) {
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
if (checkJwtRes.decoded) {
docId = checkJwtRes.decoded.docId;
} else {
ctx.logger.debug('convert-and-edit-handler invalid token %j', token);
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.VKEY), isJson);
return;
}
}
ctx.setDocId(docId);
let selectRes = yield taskResult.select(ctx, docId);
let status = yield* getConvertStatus(ctx, docId, undefined, selectRes);
if (status.end && constants.NO_ERROR === status.err) {
let fileTo = `${docId}/${constants.OUTPUT_NAME}.${targetext}`;
let metadata = yield storage.headObject(ctx, fileTo);
let streamObj = yield storage.createReadStream(ctx, fileTo);
let postRes = yield wopiClient.putRelativeFile(ctx, wopiSrc, access_token, null, streamObj.readStream, metadata.ContentLength, `.${targetext}`, true);
if (postRes) {
let fileInfo = JSON.parse(postRes.body);
status.setUrl(fileInfo.HostEditUrl);
status.setExtName('.' + targetext);
} else {
status.err = constants.UNKNOWN;
}
}
utils.fillResponse(req, res, status, isJson);
} catch (err) {
ctx.logger.error('convert-and-edit-handler error:%s', err.stack);
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson);
} finally {
ctx.logger.info('convert-and-edit-handler end');
}
});
}
exports.convertFromChanges = convertFromChanges;
exports.convertJson = convertRequestJson;
exports.convertXml = convertRequestXml;
exports.convertTo = convertTo;
exports.convertAndEdit = convertAndEdit;
exports.getConverterHtmlHandler = getConverterHtmlHandler;
exports.builder = builderRequest;

View File

@ -34,7 +34,11 @@
var mysql = require('mysql2');
var sqlBase = require('./baseConnector');
var configSql = require('config').get('services.CoAuthoring.sql');
const config = require('config');
const configSql = config.get('services.CoAuthoring.sql');
const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult');
var pool = mysql.createPool({
host : configSql.get('dbHost'),
port : configSql.get('dbPort'),
@ -102,7 +106,7 @@ exports.upsert = function(ctx, task, opt_updateUserIndex) {
let p7 = addSqlParam(cbInsert, values);
let p8 = addSqlParam(task.baseurl, values);
let p9 = addSqlParam(dateNow, values);
var sqlCommand = 'INSERT INTO task_result (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)'+
var sqlCommand = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)`+
` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8}) ON DUPLICATE KEY UPDATE` +
` last_open_date = ${p9}`;
if (task.callback) {

View File

@ -38,7 +38,10 @@ var types = require('pg').types;
var sqlBase = require('./baseConnector');
const config = require('config');
var configSql = config.get('services.CoAuthoring.sql');
const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult');
var pgPoolExtraOptions = configSql.get('pgPoolExtraOptions');
const cfgEditor = config.get('services.CoAuthoring.editor');
let connectionConfig = {
host: configSql.get('dbHost'),
port: configSql.get('dbPort'),
@ -122,19 +125,19 @@ function getUpsertString(task, values) {
if (isSupportOnConflict) {
let p9 = addSqlParam(dateNow, values);
//http://stackoverflow.com/questions/34762732/how-to-find-out-if-an-upsert-was-an-update-with-postgresql-9-5-upsert
let sqlCommand = "INSERT INTO task_result (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)";
let sqlCommand = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)`;
sqlCommand += ` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8})`;
sqlCommand += ` ON CONFLICT (tenant, id) DO UPDATE SET last_open_date = ${p9}`;
if (task.callback) {
let p10 = addSqlParam(JSON.stringify(task.callback), values);
sqlCommand += `, callback = task_result.callback || '${sqlBase.UserCallback.prototype.delimiter}{"userIndex":' `;
sqlCommand += ` || (task_result.user_index + 1)::text || ',"callback":' || ${p10}::text || '}'`;
sqlCommand += `, callback = ${cfgTableResult}.callback || '${sqlBase.UserCallback.prototype.delimiter}{"userIndex":' `;
sqlCommand += ` || (${cfgTableResult}.user_index + 1)::text || ',"callback":' || ${p10}::text || '}'`;
}
if (task.baseurl) {
let p11 = addSqlParam(task.baseurl, values);
sqlCommand += `, baseurl = ${p11}`;
}
sqlCommand += ", user_index = task_result.user_index + 1 RETURNING user_index as userindex;";
sqlCommand += `, user_index = ${cfgTableResult}.user_index + 1 RETURNING user_index as userindex;`;
return sqlCommand;
} else {
return `SELECT * FROM merge_db(${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`;
@ -182,7 +185,8 @@ exports.insertChanges = function(ctx, tableChanges, startIndex, objChanges, docI
let time = [];
//Postgres 9.4 multi-argument unnest
let sqlCommand = `INSERT INTO ${tableChanges} (tenant, id, change_id, user_id, user_id_original, user_name, change_data, change_date) `;
sqlCommand += "SELECT * FROM UNNEST ($1::text[], $2::text[], $3::int[], $4::text[], $5::text[], $6::text[], $7::text[], $8::timestamp[]);";
let changesType = cfgEditor['binaryChanges'] ? 'bytea' : 'text';
sqlCommand += `SELECT * FROM UNNEST ($1::text[], $2::text[], $3::int[], $4::text[], $5::text[], $6::text[], $7::${changesType}[], $8::timestamp[]);`;
let values = [tenant, id, changeId, userId, userIdOriginal, username, change, time];
let curLength = sqlCommand.length;
for (; i < objChanges.length; ++i) {

View File

@ -92,9 +92,24 @@ function initActive(pubsub, callback) {
}
});
pubsub.connection = conn;
pubsub.channelPublish = yield activeMQCore.openSenderPromise(conn, cfgActiveTopicPubSub);
//https://github.com/amqp/rhea/issues/251#issuecomment-535076570
let optionsPubSubSender = {
target: {
address: cfgActiveTopicPubSub,
capabilities: ['topic']
}
};
pubsub.channelPublish = yield activeMQCore.openSenderPromise(conn, optionsPubSubSender);
let receiver = yield activeMQCore.openReceiverPromise(conn, cfgActiveTopicPubSub, false);
let optionsPubSubReceiver = {
source: {
address: cfgActiveTopicPubSub,
capabilities: ['topic']
},
credit_window: 0,
autoaccept: false
};
let receiver = yield activeMQCore.openReceiverPromise(conn, optionsPubSubReceiver);
//todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1
receiver.add_credit(1);
receiver.on("message", function(context) {

View File

@ -66,6 +66,7 @@ const cfgTokenEnableBrowser = configCommon.get('services.CoAuthoring.token.enabl
const cfgTokenEnableRequestInbox = configCommon.get('services.CoAuthoring.token.enable.request.inbox');
const cfgTokenEnableRequestOutbox = configCommon.get('services.CoAuthoring.token.enable.request.outbox');
const cfgLicenseFile = configCommon.get('license.license_file');
const cfgDownloadMaxBytes = configCommon.get('FileConverter.converter.maxDownloadBytes');
const app = express();
app.disable('x-powered-by');
@ -250,9 +251,15 @@ docsCoServer.install(server, () => {
app.delete('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown);
if (cfgWopiEnable) {
//todo dest
let fileForms = multer({limits: {fieldSize: cfgDownloadMaxBytes}});
app.get('/hosting/discovery', utils.checkClientIp, wopiClient.discovery);
app.get('/hosting/capabilities', utils.checkClientIp, wopiClient.collaboraCapabilities);
app.post('/lool/convert-to/:format?', utils.checkClientIp, urleEcodedParser, fileForms.single('data'), converterService.convertTo);
app.post('/cool/convert-to/:format?', utils.checkClientIp, urleEcodedParser, fileForms.single('data'), converterService.convertTo);
app.post('/hosting/wopi/:documentType/:mode', urleEcodedParser, forms.none(), utils.lowercaseQueryString, wopiClient.getEditorHtml);
app.post('/hosting/wopi/convert-and-edit/:ext/:targetext', urleEcodedParser, forms.none(), utils.lowercaseQueryString, wopiClient.getConverterHtml);
app.get('/hosting/wopi/convert-and-edit-handler', utils.lowercaseQueryString, converterService.getConverterHtmlHandler);
}
app.post('/dummyCallback', utils.checkClientIp, rawFileParser, function(req, res){

View File

@ -36,25 +36,18 @@ const crypto = require('crypto');
var sqlBase = require('./baseConnector');
var utils = require('./../../Common/sources/utils');
var constants = require('./../../Common/sources/constants');
var commonDefines = require('./../../Common/sources/commondefines');
var tenantManager = require('./../../Common/sources/tenantManager');
var config = require('config');
const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult');
const cfgTableChanges = config.get('services.CoAuthoring.sql.tableChanges');
let addSqlParam = sqlBase.baseConnector.addSqlParameter;
let concatParams = sqlBase.baseConnector.concatParams;
var RANDOM_KEY_MAX = 10000;
var FileStatus = {
None: 0,
Ok: 1,
WaitQueue: 2,
NeedParams: 3,
Err: 5,
ErrToReload: 6,
SaveVersion: 7,
UpdateVersion: 8,
NeedPassword: 9
};
function TaskResultData() {
this.tenant = null;
this.key = null;
@ -79,7 +72,7 @@ TaskResultData.prototype.completeDefaults = function() {
this.key = '';
}
if (!this.status) {
this.status = FileStatus.None;
this.status = commonDefines.FileStatus.None;
}
if (!this.statusInfo) {
this.statusInfo = constants.NO_ERROR;
@ -113,7 +106,7 @@ function select(ctx, docId) {
let values = [];
let p1 = addSqlParam(ctx.tenant, values);
let p2 = addSqlParam(docId, values);
let sqlCommand = `SELECT * FROM task_result WHERE tenant=${p1} AND id=${p2};`;
let sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE tenant=${p1} AND id=${p2};`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
@ -182,7 +175,7 @@ function update(ctx, task, setPassword) {
let sqlSet = updateElems.join(', ');
let p1 = addSqlParam(task.tenant, values);
let p2 = addSqlParam(task.key, values);
let sqlCommand = `UPDATE task_result SET ${sqlSet} WHERE tenant=${p1} AND id=${p2};`;
let sqlCommand = `UPDATE ${cfgTableResult} SET ${sqlSet} WHERE tenant=${p1} AND id=${p2};`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
@ -202,7 +195,7 @@ function updateIf(ctx, task, mask) {
commandArgMask.push('id=' + addSqlParam(mask.key, values));
let sqlSet = commandArg.join(', ');
let sqlWhere = commandArgMask.join(' AND ');
let sqlCommand = `UPDATE task_result SET ${sqlSet} WHERE ${sqlWhere};`;
let sqlCommand = `UPDATE ${cfgTableResult} SET ${sqlSet} WHERE ${sqlWhere};`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
@ -252,7 +245,7 @@ function addRandomKey(ctx, task, opt_prefix, opt_size) {
let p6 = addSqlParam(task.changeId, values);
let p7 = addSqlParam(task.callback, values);
let p8 = addSqlParam(task.baseurl, values);
let sqlCommand = 'INSERT INTO task_result (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)' +
let sqlCommand = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)` +
` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
@ -267,7 +260,7 @@ function* addRandomKeyTask(ctx, key, opt_prefix, opt_size) {
var task = new TaskResultData();
task.tenant = ctx.tenant;
task.key = key;
task.status = FileStatus.WaitQueue;
task.status = commonDefines.FileStatus.WaitQueue;
//nTryCount чтобы не зависнуть если реально будут проблемы с DB
var nTryCount = RANDOM_KEY_MAX;
var addRes = null;
@ -294,7 +287,7 @@ function remove(ctx, docId) {
let values = [];
let p1 = addSqlParam(ctx.tenant, values);
let p2 = addSqlParam(docId, values);
const sqlCommand = `DELETE FROM task_result WHERE tenant=${p1} AND id=${p2};`;
const sqlCommand = `DELETE FROM ${cfgTableResult} WHERE tenant=${p1} AND id=${p2};`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
@ -311,7 +304,7 @@ function removeIf(ctx, mask) {
commandArgMask.push('tenant=' + addSqlParam(mask.tenant, values));
commandArgMask.push('id=' + addSqlParam(mask.key, values));
let sqlWhere = commandArgMask.join(' AND ');
const sqlCommand = `DELETE FROM task_result WHERE ${sqlWhere};`;
const sqlCommand = `DELETE FROM ${cfgTableResult} WHERE ${sqlWhere};`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
@ -328,8 +321,8 @@ function getExpired(ctx, maxCount, expireSeconds) {
utils.addSeconds(expireDate, -expireSeconds);
let sqlParam1 = addSqlParam(expireDate, values);
let sqlParam2 = addSqlParam(maxCount, values);
let sqlCommand = `SELECT * FROM task_result WHERE last_open_date <= ${sqlParam1}` +
` AND NOT EXISTS(SELECT tenant, id FROM doc_changes WHERE doc_changes.tenant = task_result.tenant AND doc_changes.id = task_result.id LIMIT 1) LIMIT ${sqlParam2};`;
let sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE last_open_date <= ${sqlParam1}` +
` AND NOT EXISTS(SELECT tenant, id FROM ${cfgTableChanges} WHERE ${cfgTableChanges}.tenant = ${cfgTableResult}.tenant AND ${cfgTableChanges}.id = ${cfgTableResult}.id LIMIT 1) LIMIT ${sqlParam2};`;
sqlBase.baseConnector.sqlQuery(ctx, sqlCommand, function(error, result) {
if (error) {
reject(error);
@ -340,7 +333,6 @@ function getExpired(ctx, maxCount, expireSeconds) {
});
}
exports.FileStatus = FileStatus;
exports.TaskResultData = TaskResultData;
exports.upsert = upsert;
exports.select = select;

View File

@ -40,6 +40,7 @@ const jwt = require('jsonwebtoken');
const config = require('config');
const utf7 = require('utf7');
const mimeDB = require('mime-db');
const xmlbuilder2 = require('xmlbuilder2');
const logger = require('./../../Common/sources/logger');
const utils = require('./../../Common/sources/utils');
const constants = require('./../../Common/sources/constants');
@ -49,6 +50,7 @@ const tenantManager = require('./../../Common/sources/tenantManager');
const sqlBase = require('./baseConnector');
const taskResult = require('./taskresult');
const canvasService = require('./canvasservice');
const converterService = require('./converterservice');
const cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm');
const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires');
@ -57,6 +59,7 @@ const cfgCallbackRequestTimeout = config.get('services.CoAuthoring.server.callba
const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout');
const cfgWopiFileInfoBlockList = config.get('wopi.fileInfoBlockList');
const cfgWopiWopiZone = config.get('wopi.wopiZone');
const cfgWopiPdfView = config.get('wopi.pdfView');
const cfgWopiWordView = config.get('wopi.wordView');
const cfgWopiWordEdit = config.get('wopi.wordEdit');
const cfgWopiCellView = config.get('wopi.cellView');
@ -98,6 +101,7 @@ let mimeTypesByExt = (function() {
function discovery(req, res) {
return co(function*() {
let output = '';
const xml = xmlbuilder2.create({version: '1.0', encoding: 'utf-8'});
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
@ -105,15 +109,19 @@ function discovery(req, res) {
let baseUrl = cfgWopiHost || utils.getBaseUrlByRequest(req);
let names = ['Word','Excel','PowerPoint'];
let favIconUrls = [cfgWopiFavIconUrlWord, cfgWopiFavIconUrlCell, cfgWopiFavIconUrlSlide];
let exts = [{view: cfgWopiWordView, edit: cfgWopiWordEdit}, {view: cfgWopiCellView, edit: cfgWopiCellEdit},
{view: cfgWopiSlideView, edit: cfgWopiSlideEdit}];
let exts = [
{targetext: 'docx', view: cfgWopiPdfView.concat(cfgWopiWordView), edit: cfgWopiWordEdit},
{targetext: 'xlsx', view: cfgWopiCellView, edit: cfgWopiCellEdit},
{targetext: 'pptx', view: cfgWopiSlideView, edit: cfgWopiSlideEdit}
];
let templateStart = `${baseUrl}/hosting/wopi`;
let templateEnd = `&amp;&lt;rs=DC_LLCC&amp;&gt;&lt;dchat=DISABLE_CHAT&amp;&gt;&lt;embed=EMBEDDED&amp;&gt;`;
templateEnd += `&lt;fs=FULLSCREEN&amp;&gt;&lt;hid=HOST_SESSION_ID&amp;&gt;&lt;rec=RECORDING&amp;&gt;`;
templateEnd += `&lt;sc=SESSION_CONTEXT&amp;&gt;&lt;thm=THEME_ID&amp;&gt;&lt;ui=UI_LLCC&amp;&gt;`;
templateEnd += `&lt;wopisrc=WOPI_SOURCE&amp;&gt;&amp;`;
let documentTypes = [`word`, `cell`, `slide`];
output += `<?xml version="1.0" encoding="utf-8"?><wopi-discovery><net-zone name="${cfgWopiWopiZone}">`;
let xmlZone = xml.ele('wopi-discovery').ele('net-zone', { name: cfgWopiWopiZone });
//start section for MS WOPI connectors
for(let i = 0; i < names.length; ++i) {
let name = names[i];
@ -125,21 +133,25 @@ function discovery(req, res) {
let urlTemplateView = `${templateStart}/${documentTypes[i]}/view?${templateEnd}`;
let urlTemplateEmbedView = `${templateStart}/${documentTypes[i]}/view?embed=1${templateEnd}`;
let urlTemplateEdit = `${templateStart}/${documentTypes[i]}/edit?${templateEnd}`;
output +=`<app name="${name}" favIconUrl="${favIconUrl}">`;
let xmlApp = xmlZone.ele('app', {name: name, favIconUrl: favIconUrl});
for (let j = 0; j < ext.view.length; ++j) {
output += `<action name="view" ext="${ext.view[j]}" urlsrc="${urlTemplateView}" />`;
output += `<action name="embedview" ext="${ext.view[j]}" urlsrc="${urlTemplateEmbedView}" />`;
xmlApp.ele('action', {name: 'view', ext: ext.view[j], urlsrc: urlTemplateView}).up();
xmlApp.ele('action', {name: 'embedview', ext: ext.view[j], urlsrc: urlTemplateEmbedView}).up();
if (-1 === cfgWopiPdfView.indexOf(ext.view[j])) {
let urlConvert = `${templateStart}/convert-and-edit/${ext.view[j]}/${ext.targetext}?${templateEnd}`;
xmlApp.ele('action', {name: 'convert', ext: ext.view[j], targetext: ext.targetext, requires: 'update', urlsrc: urlConvert}).up();
}
}
for (let j = 0; j < ext.edit.length; ++j) {
output += `<action name="view" ext="${ext.edit[j]}" urlsrc="${urlTemplateView}" />`;
output += `<action name="embedview" ext="${ext.edit[j]}" urlsrc="${urlTemplateEmbedView}" />`;
xmlApp.ele('action', {name: 'view', ext: ext.edit[j], urlsrc: urlTemplateView}).up();
xmlApp.ele('action', {name: 'embedview', ext: ext.edit[j], urlsrc: urlTemplateEmbedView}).up();
if ("oform" !== ext.edit[j]) {
//todo config
output += `<action name="editnew" ext="${ext.edit[j]}" requires="locks,update" urlsrc="${urlTemplateEdit}" />`;
xmlApp.ele('action', {name: 'editnew', ext: ext.edit[j], requires: 'locks,update', urlsrc: urlTemplateEdit}).up();
}
output += `<action name="edit" ext="${ext.edit[j]}" default="true" requires="locks,update" urlsrc="${urlTemplateEdit}" />`;
xmlApp.ele('action', {name: 'edit', ext: ext.edit[j], default: 'true', requires: 'locks,update', urlsrc: urlTemplateEdit}).up();
}
output +=`</app>`;
xmlApp.up();
}
//end section for MS WOPI connectors
//start section for collabora nexcloud connectors
@ -152,10 +164,14 @@ function discovery(req, res) {
let mimeTypes = mimeTypesByExt[ext.view[j]];
if (mimeTypes) {
mimeTypes.forEach((value) => {
output += `<app name="${value}">`;
output += `<action name="view" ext="" default="true" urlsrc="${urlTemplateView}" />`;
output += `<action name="embedview" ext="" urlsrc="${urlTemplateEmbedView}" />`;
output += `</app>`;
let xmlApp = xmlZone.ele('app', {name: value});
xmlApp.ele('action', {name: 'view', ext: '', default: 'true', urlsrc: urlTemplateView}).up();
xmlApp.ele('action', {name: 'embedview', ext: '', urlsrc: urlTemplateEmbedView}).up();
if (-1 === cfgWopiPdfView.indexOf(ext.view[j])) {
let urlConvert = `${templateStart}/convert-and-edit/${ext.view[j]}/${ext.targetext}?${templateEnd}`;
xmlApp.ele('action', {name: 'convert', ext: '', targetext: ext.targetext, requires: 'update', urlsrc: urlConvert}).up();
}
xmlApp.up();
});
}
}
@ -163,29 +179,30 @@ function discovery(req, res) {
let mimeTypes = mimeTypesByExt[ext.edit[j]];
if (mimeTypes) {
mimeTypes.forEach((value) => {
output +=`<app name="${value}">`;
output += `<action name="edit" ext="" default="true" requires="locks,update" urlsrc="${urlTemplateEdit}" />`;
output +=`</app>`;
let xmlApp = xmlZone.ele('app', {name: value});
xmlApp.ele('action', {name: 'edit', ext: '', default: 'true', requires: 'locks,update', urlsrc: urlTemplateEdit}).up();
xmlApp.up();
});
}
}
}
output += `<app name="Capabilities">`;
output += `<action ext="" name="getinfo" urlsrc="${baseUrl}/hosting/capabilities"/>`;
output += `</app>`;
let xmlApp = xmlZone.ele('app', {name: 'Capabilities'});
xmlApp.ele('action', {ext: '', name: 'getinfo', requires: 'locks,update', urlsrc: `${baseUrl}/hosting/capabilities`}).up();
xmlApp.up();
//end section for collabora nexcloud connectors
let proofKey = ``;
let xmlDiscovery = xmlZone.up();
if (cfgWopiPublicKeyOld && cfgWopiPublicKey) {
proofKey += `<proof-key oldvalue="${cfgWopiPublicKeyOld}" oldmodulus="${cfgWopiModulusOld}" `;
proofKey += `oldexponent="${cfgWopiExponentOld}" value="${cfgWopiPublicKey}" modulus="${cfgWopiModulus}" `;
proofKey += `exponent="${cfgWopiExponent}"/>`;
xmlDiscovery.ele('proof-key', {
oldvalue: cfgWopiPublicKeyOld, oldmodulus: cfgWopiModulusOld, oldexponent: cfgWopiExponentOld,
value: cfgWopiPublicKey, modulus: cfgWopiModulus, exponent: cfgWopiExponent
}).up();
}
output += `</net-zone>${proofKey}</wopi-discovery>`;
xmlDiscovery.up();
} catch (err) {
ctx.logger.error('wopiDiscovery error:%s', err.stack);
} finally {
res.setHeader('Content-Type', 'text/xml');
res.send(output);
res.send(xml.end());
ctx.logger.info('wopiDiscovery end');
}
});
@ -193,7 +210,7 @@ function discovery(req, res) {
function collaboraCapabilities(req, res) {
return co(function*() {
let output = {
"convert-to": {"available": false}, "hasMobileSupport": true, "hasProxyPrefix": false, "hasTemplateSaveAs": false,
"convert-to": {"available": true, "endpoint":"/lool/convert-to"}, "hasMobileSupport": true, "hasProxyPrefix": false, "hasTemplateSaveAs": false,
"hasTemplateSource": true, "productVersion": commonDefines.buildVersion
};
let ctx = new operationContext.Context();
@ -221,6 +238,9 @@ function isWopiModifiedMarker(url) {
}
}
function getWopiUnlockMarker(wopiParams) {
if (!wopiParams.userAuth || !wopiParams.commonInfo) {
return;
}
return JSON.stringify(Object.assign({unlockId: wopiParams.commonInfo.lockId}, wopiParams.userAuth));
}
function getWopiModifiedMarker(wopiParams, lastModifiedTime) {
@ -251,10 +271,14 @@ function parseWopiCallback(ctx, userAuthStr, opt_url) {
let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, opt_url, 1);
if (isWopiCallback(commonInfoStr)) {
commonInfo = JSON.parse(commonInfoStr);
lastModifiedTime = commonInfo.fileInfo.LastModifiedTime;
if (lastModifiedTime) {
let callbacks = sqlBase.UserCallback.prototype.getCallbacks(ctx, opt_url);
lastModifiedTime = getLastModifiedTimeFromCallbacks(callbacks);
if (commonInfo.fileInfo) {
lastModifiedTime = commonInfo.fileInfo.LastModifiedTime;
if (lastModifiedTime) {
let callbacks = sqlBase.UserCallback.prototype.getCallbacks(ctx, opt_url);
lastModifiedTime = getLastModifiedTimeFromCallbacks(callbacks);
}
} else {
commonInfo = null;
}
}
}
@ -328,9 +352,8 @@ function getEditorHtml(req, res) {
let access_token = req.body['access_token'] || "";
let access_token_ttl = parseInt(req.body['access_token_ttl']) || 0;
let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`;
let fileInfo = params.fileInfo = yield checkFileInfo(ctx, uri, access_token, sc);
let fileInfo = params.fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token, sc);
if (!fileInfo) {
params.fileInfo = {};
return;
@ -394,7 +417,7 @@ function getEditorHtml(req, res) {
if (cfgTokenEnableBrowser) {
let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires};
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Inbox);
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
params.token = jwt.sign(params, secret, options);
}
} catch (err) {
@ -412,12 +435,70 @@ function getEditorHtml(req, res) {
}
});
}
function getConverterHtml(req, res) {
return co(function*() {
let params = {statusHandler: undefined};
let ctx = new operationContext.Context();
try {
ctx.initFromRequest(req);
let wopiSrc = req.query['wopisrc'];
let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1);
ctx.setDocId(fileId);
ctx.logger.info('convert-and-edit start');
let access_token = req.body['access_token'] || "";
let access_token_ttl = parseInt(req.body['access_token_ttl']) || 0;
let ext = req.params.ext;
let targetext = req.params.targetext;
if (!(wopiSrc && access_token && access_token_ttl && ext && targetext)) {
ctx.logger.debug('convert-and-edit invalid params: wopiSrc=%s; access_token=%s; access_token_ttl=%s; ext=%s; targetext=%s', wopiSrc, access_token, access_token_ttl, ext, targetext);
return;
}
let fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token);
if (!fileInfo) {
ctx.logger.info('convert-and-edit checkFileInfo error');
return;
}
let wopiParams = getWopiParams(null, fileInfo, wopiSrc, access_token, access_token_ttl);
let docId = yield converterService.convertAndEdit(ctx, wopiParams, ext, targetext);
if (docId) {
let baseUrl = cfgWopiHost || utils.getBaseUrlByRequest(req);
params.statusHandler = `${baseUrl}/hosting/wopi/convert-and-edit-handler`;
params.statusHandler += `?wopiSrc=${encodeURI(wopiSrc)}&access_token=${encodeURI(access_token)}`;
params.statusHandler += `&targetext=${encodeURI(targetext)}&docId=${encodeURI(docId)}`;
if (cfgTokenEnableBrowser) {
let tokenData = {docId: docId};
let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires};
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
let token = jwt.sign(tokenData, secret, options);
params.statusHandler += `&token=${encodeURI(token)}`;
}
}
} catch (err) {
ctx.logger.error('convert-and-edit error:%s', err.stack);
} finally {
ctx.logger.debug('convert-and-edit render params=%j', params);
try {
res.render("convert-and-edit-wopi", params);
} catch (err) {
ctx.logger.error('convert-and-edit error:%s', err.stack);
res.sendStatus(400);
}
ctx.logger.info('convert-and-edit end');
}
});
}
function putFile(ctx, wopiParams, data, dataStream, dataSize, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) {
return co(function* () {
let postRes = null;
try {
ctx.logger.info('wopi PutFile start');
if (!wopiParams.userAuth) {
if (!wopiParams.userAuth || !wopiParams.commonInfo) {
return postRes;
}
let fileInfo = wopiParams.commonInfo.fileInfo;
@ -457,12 +538,40 @@ function putFile(ctx, wopiParams, data, dataStream, dataSize, userLastChangeId,
return postRes;
});
}
function putRelativeFile(ctx, wopiSrc, access_token, data, dataStream, dataSize, suggestedTarget, isFileConversion) {
return co(function* () {
let postRes = null;
try {
ctx.logger.info('wopi putRelativeFile start');
let uri = `${wopiSrc}?access_token=${access_token}`;
let filterStatus = yield checkIpFilter(ctx, uri);
if (0 !== filterStatus) {
return postRes;
}
let headers = {'X-WOPI-Override': 'PUT_RELATIVE', 'X-WOPI-SuggestedTarget': utf7.encode(suggestedTarget),
'X-WOPI-FileConversion': isFileConversion};
fillStandardHeaders(headers, uri, access_token);
ctx.logger.debug('wopi putRelativeFile request uri=%s headers=%j', uri, headers);
postRes = yield utils.postRequestPromise(uri, data, dataStream, dataSize, cfgCallbackRequestTimeout, undefined, headers);
ctx.logger.debug('wopi putRelativeFile response headers=%j', postRes.response.headers);
ctx.logger.debug('wopi putRelativeFile response body:%s', postRes.body);
} catch (err) {
ctx.logger.error('wopi error putRelativeFile:%s', err.stack);
} finally {
ctx.logger.info('wopi putRelativeFile end');
}
return postRes;
});
}
function renameFile(ctx, wopiParams, name) {
return co(function* () {
let res = undefined;
try {
ctx.logger.info('wopi RenameFile start');
if (!wopiParams.userAuth) {
if (!wopiParams.userAuth || !wopiParams.commonInfo) {
return res;
}
let fileInfo = wopiParams.commonInfo.fileInfo;
@ -501,18 +610,19 @@ function renameFile(ctx, wopiParams, name) {
return res;
});
}
function checkFileInfo(ctx, uri, access_token, sc) {
function checkFileInfo(ctx, wopiSrc, access_token, opt_sc) {
return co(function* () {
let fileInfo = undefined;
try {
ctx.logger.info('wopi checkFileInfo start');
let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`;
let filterStatus = yield checkIpFilter(ctx, uri);
if (0 !== filterStatus) {
return fileInfo;
}
let headers = {};
if (sc) {
headers['X-WOPI-SessionContext'] = sc;
if (opt_sc) {
headers['X-WOPI-SessionContext'] = opt_sc;
}
fillStandardHeaders(headers, uri, access_token);
ctx.logger.debug('wopi checkFileInfo request uri=%s headers=%j', uri, headers);
@ -565,11 +675,11 @@ function unlock(ctx, wopiParams) {
return co(function* () {
try {
ctx.logger.info('wopi Unlock start');
if (!wopiParams.userAuth || !wopiParams.commonInfo) {
return;
}
let fileInfo = wopiParams.commonInfo.fileInfo;
if (fileInfo && fileInfo.SupportsLocks) {
if (!wopiParams.userAuth) {
return;
}
let wopiSrc = wopiParams.userAuth.wopiSrc;
let lockId = wopiParams.commonInfo.lockId;
let access_token = wopiParams.userAuth.access_token;
@ -650,12 +760,22 @@ function checkIpFilter(ctx, uri){
return filterStatus;
});
}
function getWopiParams(lockId, fileInfo, wopiSrc, access_token, access_token_ttl) {
let commonInfo = {lockId: lockId, fileInfo: fileInfo};
let userAuth = {
wopiSrc: wopiSrc, access_token: access_token, access_token_ttl: access_token_ttl,
hostSessionId: null, userSessionId: null, mode: null
};
return {commonInfo: commonInfo, userAuth: userAuth, LastModifiedTime: null};
};
exports.discovery = discovery;
exports.collaboraCapabilities = collaboraCapabilities;
exports.parseWopiCallback = parseWopiCallback;
exports.getEditorHtml = getEditorHtml;
exports.getConverterHtml = getConverterHtml;
exports.putFile = putFile;
exports.putRelativeFile = putRelativeFile;
exports.renameFile = renameFile;
exports.lock = lock;
exports.unlock = unlock;

View File

@ -51,9 +51,9 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"json5": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"requires": {
"minimist": "^1.2.0"
}
@ -76,9 +76,9 @@
}
},
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
},
"pseudomap": {
"version": "1.0.2",

View File

@ -35,7 +35,6 @@ var os = require('os');
var path = require('path');
var fs = require('fs');
var url = require('url');
var childProcess = require('child_process');
var co = require('co');
var config = require('config');
var spawnAsync = require('@expo/spawn-async');
@ -50,6 +49,7 @@ var logger = require('./../../Common/sources/logger');
var constants = require('./../../Common/sources/constants');
var baseConnector = require('./../../DocService/sources/baseConnector');
const wopiClient = require('./../../DocService/sources/wopiClient');
const taskResult = require('./../../DocService/sources/taskresult');
var statsDClient = require('./../../Common/sources/statsdclient');
var queueService = require('./../../Common/sources/taskqueueRabbitMQ');
const formatChecker = require('./../../Common/sources/formatchecker');
@ -77,6 +77,7 @@ const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestC
const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles');
const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname');
const cfgNewFileTemplate = config.get('services.CoAuthoring.server.newFileTemplate');
const cfgEditor = config.get('services.CoAuthoring.editor');
//windows limit 512(2048) https://msdn.microsoft.com/en-us/library/6e3b887c.aspx
//Ubuntu 14.04 limit 4096 http://underyx.me/2015/05/18/raising-the-maximum-number-of-file-descriptors.html
@ -281,6 +282,17 @@ function getTempDir() {
fs.mkdirSync(resultDir);
return {temp: newTemp, source: sourceDir, result: resultDir};
}
function* isUselessConvertion(ctx, task, cmd) {
if (task.getFromChanges() && 'sfc' === cmd.getCommand()) {
let selectRes = yield taskResult.select(ctx, cmd.getDocId());
let row = selectRes.length > 0 ? selectRes[0] : null;
if (utils.isUselesSfc(row, cmd)) {
ctx.logger.warn('isUselessConvertion return true. row=%j', row);
return constants.CONVERT_PARAMS;
}
}
return constants.NO_ERROR;
}
function* replaceEmptyFile(ctx, fileFrom, ext, _lcid) {
if (!fs.existsSync(fileFrom) || 0 === fs.lstatSync(fileFrom).size) {
let locale = 'en-US';
@ -404,7 +416,11 @@ function* processDownloadFromStorage(ctx, dataConvert, cmd, task, tempDirs, auth
yield* concatFiles(tempDirs.source);
}
if (task.getFromChanges()) {
res = yield* processChanges(ctx, tempDirs, cmd, authorProps);
if(cfgEditor['binaryChanges']) {
res = yield* processChangesBin(ctx, tempDirs, task, cmd, authorProps);
} else {
res = yield* processChangesBase64(ctx, tempDirs, task, cmd, authorProps);
}
}
//todo rework
if (!fs.existsSync(dataConvert.fileFrom)) {
@ -446,8 +462,125 @@ function* concatFiles(source) {
}
}
}
function* processChangesBin(ctx, tempDirs, task, cmd, authorProps) {
let res = constants.NO_ERROR;
let changesDir = path.join(tempDirs.source, constants.CHANGES_NAME);
fs.mkdirSync(changesDir);
let indexFile = 0;
let changesAuthor = null;
let changesAuthorUnique = null;
let changesIndex = null;
let changesHistory = {
serverVersion: commonDefines.buildVersion,
changes: []
};
let forceSave = cmd.getForceSave();
let forceSaveTime;
let forceSaveIndex = Number.MAX_VALUE;
if (forceSave && undefined !== forceSave.getTime() && undefined !== forceSave.getIndex()) {
forceSaveTime = forceSave.getTime();
forceSaveIndex = forceSave.getIndex();
}
let extChangeInfo = cmd.getExternalChangeInfo();
let extChanges;
if (extChangeInfo) {
extChanges = [{
id: cmd.getDocId(), change_id: 0, change_data: Buffer.alloc(0), user_id: extChangeInfo.user_id,
user_id_original: extChangeInfo.user_id_original, user_name: extChangeInfo.user_name,
change_date: new Date(extChangeInfo.change_date)
}];
}
function* processChanges(ctx, tempDirs, cmd, authorProps) {
let streamObj = yield* streamCreateBin(ctx, changesDir, indexFile++, {highWaterMark: cfgStreamWriterBufferSize});
yield* streamWriteBin(streamObj, Buffer.from(utils.getChangesFileHeader(), 'utf-8'));
let curIndexStart = 0;
let curIndexEnd = Math.min(curIndexStart + cfgMaxRequestChanges, forceSaveIndex);
while (curIndexStart < curIndexEnd || extChanges) {
let changes = [];
if (curIndexStart < curIndexEnd) {
changes = yield baseConnector.getChangesPromise(ctx, cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime);
if (changes.length > 0 && changes[0].change_data.subarray(0, 'ENCRYPTED;'.length).includes('ENCRYPTED;')) {
ctx.logger.warn('processChanges encrypted changes');
//todo sql request instead?
res = constants.EDITOR_CHANGES;
}
res = yield* isUselessConvertion(ctx, task, cmd);
if (constants.NO_ERROR !== res) {
break;
}
}
if (0 === changes.length && extChanges) {
changes = extChanges;
}
extChanges = undefined;
for (let i = 0; i < changes.length; ++i) {
let change = changes[i];
if (null === changesAuthor || changesAuthor !== change.user_id_original) {
if (null !== changesAuthor) {
yield* streamEndBin(streamObj);
streamObj = yield* streamCreateBin(ctx, changesDir, indexFile++);
yield* streamWriteBin(streamObj, Buffer.from(utils.getChangesFileHeader(), 'utf-8'));
}
let strDate = baseConnector.getDateTime(change.change_date);
changesHistory.changes.push({'created': strDate, 'user': {'id': change.user_id_original, 'name': change.user_name}});
}
changesAuthor = change.user_id_original;
changesAuthorUnique = change.user_id;
yield* streamWriteBin(streamObj, change.change_data);
streamObj.isNoChangesInFile = false;
}
if (changes.length > 0) {
authorProps.lastModifiedBy = changes[changes.length - 1].user_name;
authorProps.modified = changes[changes.length - 1].change_date.toISOString().slice(0, 19) + 'Z';
}
if (changes.length === curIndexEnd - curIndexStart) {
curIndexStart += cfgMaxRequestChanges;
curIndexEnd = Math.min(curIndexStart + cfgMaxRequestChanges, forceSaveIndex);
} else {
break;
}
}
yield* streamEndBin(streamObj);
if (streamObj.isNoChangesInFile) {
fs.unlinkSync(streamObj.filePath);
}
if (null !== changesAuthorUnique) {
changesIndex = utils.getIndexFromUserId(changesAuthorUnique, changesAuthor);
}
if (null == changesAuthor && null == changesIndex && forceSave && undefined !== forceSave.getAuthorUserId() &&
undefined !== forceSave.getAuthorUserIndex()) {
changesAuthor = forceSave.getAuthorUserId();
changesIndex = forceSave.getAuthorUserIndex();
}
cmd.setUserId(changesAuthor);
cmd.setUserIndex(changesIndex);
fs.writeFileSync(path.join(tempDirs.result, 'changesHistory.json'), JSON.stringify(changesHistory), 'utf8');
ctx.logger.debug('processChanges end');
return res;
}
function* streamCreateBin(ctx, changesDir, indexFile, opt_options) {
let fileName = constants.CHANGES_NAME + indexFile + '.bin';
let filePath = path.join(changesDir, fileName);
let writeStream = yield utils.promiseCreateWriteStream(filePath, opt_options);
writeStream.on('error', function(err) {
//todo integrate error handle in main thread (probable: set flag here and check it in main thread)
ctx.logger.error('WriteStreamError %s', err.stack);
});
return {writeStream: writeStream, filePath: filePath, isNoChangesInFile: true};
}
function* streamWriteBin(streamObj, buf) {
if (!streamObj.writeStream.write(buf)) {
yield utils.promiseWaitDrain(streamObj.writeStream);
}
}
function* streamEndBin(streamObj) {
streamObj.writeStream.end();
yield utils.promiseWaitClose(streamObj.writeStream);
}
function* processChangesBase64(ctx, tempDirs, task, cmd, authorProps) {
let res = constants.NO_ERROR;
let changesDir = path.join(tempDirs.source, constants.CHANGES_NAME);
fs.mkdirSync(changesDir);
@ -483,6 +616,15 @@ function* processChanges(ctx, tempDirs, cmd, authorProps) {
let changes = [];
if (curIndexStart < curIndexEnd) {
changes = yield baseConnector.getChangesPromise(ctx, cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime);
if (changes.length > 0 && changes[0].change_data.startsWith('ENCRYPTED;')) {
ctx.logger.warn('processChanges encrypted changes');
//todo sql request instead?
res = constants.EDITOR_CHANGES;
}
res = yield* isUselessConvertion(ctx, task, cmd);
if (constants.NO_ERROR !== res) {
break;
}
}
if (0 === changes.length && extChanges) {
changes = extChanges;
@ -490,12 +632,6 @@ function* processChanges(ctx, tempDirs, cmd, authorProps) {
extChanges = undefined;
for (let i = 0; i < changes.length; ++i) {
let change = changes[i];
if (change.change_data.startsWith('ENCRYPTED;')) {
ctx.logger.warn('processChanges encrypted changes');
//todo sql request instead?
res = constants.EDITOR_CHANGES;
break;
}
if (null === changesAuthor || changesAuthor !== change.user_id_original) {
if (null !== changesAuthor) {
yield* streamEnd(streamObj, ']');
@ -744,7 +880,10 @@ function* ExecuteTask(ctx, task) {
dataConvert.fileTo = fileTo ? path.join(tempDirs.result, fileTo) : '';
let isBuilder = cmd.getIsBuilder();
let authorProps = {lastModifiedBy: null, modified: null};
if (cmd.getUrl()) {
error = yield* isUselessConvertion(ctx, task, cmd);
if (constants.NO_ERROR !== error) {
;
} else if (cmd.getUrl()) {
let format = cmd.getFormat();
dataConvert.fileFrom = path.join(tempDirs.source, dataConvert.key + '.' + format);
if (utils.checkPathTraversal(ctx, dataConvert.key, tempDirs.source, dataConvert.fileFrom)) {
@ -757,16 +896,17 @@ function* ExecuteTask(ctx, task) {
if (wopiParams) {
withAuthorization = false;
filterPrivate = false;
let fileInfo = wopiParams.commonInfo.fileInfo;
let fileInfo = wopiParams.commonInfo?.fileInfo;
let userAuth = wopiParams.userAuth;
fileSize = fileInfo.Size;
if (fileInfo.FileUrl) {
fileSize = fileInfo?.Size;
if (fileInfo?.FileUrl) {
//Requests to the FileUrl can not be signed using proof keys. The FileUrl is used exactly as provided by the host, so it does not necessarily include the access token, which is required to construct the expected proof.
url = fileInfo.FileUrl;
} else if (fileInfo.TemplateSource) {
} else if (fileInfo?.TemplateSource) {
url = fileInfo.TemplateSource;
} else if (userAuth) {
url = `${userAuth.wopiSrc}/contents?access_token=${userAuth.access_token}`;
headers = {'X-WOPI-MaxExpectedSize': cfgDownloadMaxBytes, 'X-WOPI-ItemVersion': fileInfo.Version};
headers = {'X-WOPI-MaxExpectedSize': cfgDownloadMaxBytes};
wopiClient.fillStandardHeaders(headers, url, userAuth.access_token);
}
ctx.logger.debug('wopi url=%s; headers=%j', url, headers);

View File

@ -1,19 +0,0 @@
Copyright (c) 2011-2012 VMware, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

22
license/SocketIO.license Normal file
View File

@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) 2014-2018 Automattic <dev@cloudup.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

110
npm-shrinkwrap.json generated
View File

@ -30,7 +30,7 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
}
}
},
@ -52,7 +52,7 @@
"array-each": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
"integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8="
"integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA=="
},
"array-slice": {
"version": "1.1.0",
@ -70,9 +70,9 @@
"integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c="
},
"async": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz",
"integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g=="
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
},
"atob": {
"version": "2.1.2",
@ -80,9 +80,9 @@
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base": {
"version": "0.11.2",
@ -334,7 +334,7 @@
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
"integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w=="
},
"component-emitter": {
"version": "1.3.0",
@ -344,7 +344,7 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"copy-descriptor": {
"version": "0.1.1",
@ -424,12 +424,12 @@
"eventemitter2": {
"version": "0.4.14",
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz",
"integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas="
"integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ=="
},
"exit": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw="
"integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="
},
"expand-brackets": {
"version": "2.1.4",
@ -583,7 +583,7 @@
"findup-sync": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz",
"integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=",
"integrity": "sha512-z8Nrwhi6wzxNMIbxlrTzuUW6KWuKkogZ/7OdDVq+0+kxn77KUH1nipx8iU6suqkHqc4y6n7a9A8IpmxY/pTjWg==",
"requires": {
"glob": "~5.0.0"
},
@ -591,7 +591,7 @@
"glob": {
"version": "5.0.15",
"resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
"integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
"integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==",
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
@ -627,7 +627,7 @@
"for-own": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
"integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
"integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==",
"requires": {
"for-in": "^1.0.1"
}
@ -701,16 +701,16 @@
"integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ=="
},
"grunt": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/grunt/-/grunt-1.4.1.tgz",
"integrity": "sha512-ZXIYXTsAVrA7sM+jZxjQdrBOAg7DyMUplOMhTaspMRExei+fD0BTwdWXnn0W5SXqhb/Q/nlkzXclSi3IH55PIA==",
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.3.tgz",
"integrity": "sha512-mKwmo4X2d8/4c/BmcOETHek675uOqw0RuA/zy12jaspWqvTp4+ZeQF1W+OTpcbncnaBsfbQJ6l0l4j+Sn/GmaQ==",
"requires": {
"dateformat": "~3.0.3",
"eventemitter2": "~0.4.13",
"exit": "~0.1.2",
"findup-sync": "~0.3.0",
"glob": "~7.1.6",
"grunt-cli": "~1.4.2",
"grunt-cli": "~1.4.3",
"grunt-known-options": "~2.0.0",
"grunt-legacy-log": "~3.0.0",
"grunt-legacy-util": "~2.0.1",
@ -744,6 +744,14 @@
}
}
}
},
"minimatch": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
"integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
"requires": {
"brace-expansion": "^1.1.7"
}
}
}
},
@ -950,7 +958,7 @@
"hooker": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz",
"integrity": "sha1-uDT3I8xKJCqmWWNFnfbZhMXT2Vk="
"integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA=="
},
"iconv-lite": {
"version": "0.4.24",
@ -982,7 +990,7 @@
"interpret": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz",
"integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ="
"integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA=="
},
"is-absolute": {
"version": "1.0.0",
@ -1017,9 +1025,9 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-core-module": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz",
"integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
"integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
"requires": {
"has": "^1.0.3"
}
@ -1209,12 +1217,12 @@
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"micromatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.2.3"
"braces": "^3.0.2",
"picomatch": "^2.3.1"
}
},
"to-regex-range": {
@ -1284,9 +1292,9 @@
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}
@ -1321,9 +1329,9 @@
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"mout": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/mout/-/mout-1.2.3.tgz",
"integrity": "sha512-vtE+eZcSj/sBkIp6gxB87MznryWP+gHIp0XX9SKrzA5TAkvz6y7VTuNruBjYdJozd8NY5i9XVIsn8cn3SwNjzg=="
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/mout/-/mout-1.2.4.tgz",
"integrity": "sha512-mZb9uOruMWgn/fw28DG4/yE3Kehfk1zKCLhuDU2O3vlKdnBBr4XaOCqVTflJ5aODavGUPqFHZgrFX3NJVuxGhQ=="
},
"ms": {
"version": "2.0.0",
@ -1351,7 +1359,7 @@
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==",
"requires": {
"abbrev": "1"
}
@ -1395,7 +1403,7 @@
"object.defaults": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz",
"integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=",
"integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==",
"requires": {
"array-each": "^1.0.1",
"array-slice": "^1.0.0",
@ -1406,7 +1414,7 @@
"object.map": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz",
"integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=",
"integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==",
"requires": {
"for-own": "^1.0.0",
"make-iterator": "^1.0.0"
@ -1450,7 +1458,7 @@
"parse-filepath": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz",
"integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=",
"integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==",
"requires": {
"is-absolute": "^1.0.0",
"map-cache": "^0.2.0",
@ -1480,7 +1488,7 @@
"path-root": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
"integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=",
"integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==",
"requires": {
"path-root-regex": "^0.1.0"
}
@ -1488,7 +1496,7 @@
"path-root-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz",
"integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0="
"integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ=="
},
"picomatch": {
"version": "2.3.1",
@ -1528,11 +1536,11 @@
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
"resolve": {
"version": "1.21.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.21.0.tgz",
"integrity": "sha512-3wCbTpk5WJlyE4mSOtDLhqQmGFi0/TD9VPwmiolnk8U0wRgMEktqCXd3vy5buTO3tljvalNvKrjHEfrd2WpEKA==",
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
"integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
"requires": {
"is-core-module": "^2.8.0",
"is-core-module": "^2.9.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
@ -1813,14 +1821,14 @@
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
"integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo="
"integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg=="
},
"underscore.string": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.5.tgz",
"integrity": "sha512-g+dpmgn+XBneLmXXo+sGlW5xQEt4ErkS3mgeN2GFbremYeMBSJKr9Wf2KJplQVaiPY/f7FN6atosWYNm9ovrYg==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz",
"integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==",
"requires": {
"sprintf-js": "^1.0.3",
"sprintf-js": "^1.1.1",
"util-deprecate": "^1.0.2"
}
},
@ -1892,7 +1900,7 @@
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"v8flags": {
"version": "3.2.0",

View File

@ -54,7 +54,7 @@
"./build/server/Metrics"
],
"dependencies": {
"grunt": "^1.4.1",
"grunt": "^1.5.3",
"grunt-banner": "^0.6.0",
"grunt-check-dependencies": "^1.0.0",
"grunt-contrib-clean": "^2.0.0",

View File

@ -44,6 +44,30 @@ CREATE TABLE IF NOT EXISTS `doc_changes` (
/*!40000 ALTER TABLE `doc_changes` DISABLE KEYS */;
/*!40000 ALTER TABLE `doc_changes` ENABLE KEYS */;
--
-- Definition of table `doc_changes`
--
CREATE TABLE IF NOT EXISTS `doc_changes2` (
`tenant` varchar(255) NOT NULL,
`id` varchar(255) NOT NULL,
`change_id` int(10) unsigned NOT NULL,
`user_id` varchar(255) NOT NULL,
`user_id_original` varchar(255) NOT NULL,
`user_name` varchar(255) NOT NULL,
`change_data` longblob NOT NULL,
`change_date` datetime NOT NULL,
PRIMARY KEY (`tenant`, `id`,`change_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Dumping data for table `doc_changes`
--
/*!40000 ALTER TABLE `doc_changes2` DISABLE KEYS */;
/*!40000 ALTER TABLE `doc_changes2` ENABLE KEYS */;
--
-- Definition of table `task_result`
--

View File

@ -0,0 +1,18 @@
DELIMITER DLM00
DROP PROCEDURE IF EXISTS upgrade730 DLM00
CREATE PROCEDURE upgrade730()
BEGIN
IF (SELECT DATA_TYPE FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'doc_changes' AND COLUMN_NAME = 'change_data') <> 'longblob' THEN
SET SQL_SAFE_UPDATES=0;
ALTER TABLE `doc_changes` CHANGE COLUMN `change_data` `change_data` LONGBLOB NOT NULL ;
SET SQL_SAFE_UPDATES=1;
END IF;
END DLM00
CALL upgrade730() DLM00
DELIMITER ;

View File

@ -20,6 +20,22 @@ PRIMARY KEY ("tenant", "id", "change_id")
)
WITH (OIDS=FALSE);
-- ----------------------------
-- Table structure for doc_changes2
-- ----------------------------
CREATE TABLE IF NOT EXISTS "public"."doc_changes2" (
"tenant" varchar(255) COLLATE "default" NOT NULL,
"id" varchar(255) COLLATE "default" NOT NULL,
"change_id" int4 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" bytea NOT NULL,
"change_date" timestamp without time zone NOT NULL,
PRIMARY KEY ("tenant", "id", "change_id")
)
WITH (OIDS=FALSE);
-- ----------------------------
-- Table structure for task_result
-- ----------------------------

View File

@ -0,0 +1,9 @@
DO $$
BEGIN
BEGIN
ALTER TABLE doc_changes ALTER COLUMN change_data TYPE bytea USING change_data::bytea;
EXCEPTION
WHEN duplicate_column THEN RAISE NOTICE 'cant modify doc_changes.change_data colummn';
END;
END;
$$