mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-04-07 14:04:35 +08:00
Merge remote-tracking branch 'remotes/origin/release/v9.1.0' into feature/linter-mysql
# Conflicts: # .github/workflows/azureStorageTests.yml # .github/workflows/s3storageTests.yml # Common/config/default.json # Common/config/log4js/development.json # Common/config/log4js/production.json # Common/sources/constants.js # Common/sources/logger.js # Common/sources/moduleReloader.js # Common/sources/operationContext.js # Common/sources/storage/storage-az.js # Common/sources/storage/storage-fs.js # Common/sources/utils.js # DocService/sources/DocsCoServer.js # DocService/sources/ai/aiProxyHandler.js # DocService/sources/canvasservice.js # DocService/sources/converterservice.js # DocService/sources/databaseConnectors/oracleConnector.js # DocService/sources/fileuploaderservice.js # DocService/sources/wopiClient.js # DocService/sources/wopiUtils.js # FileConverter/sources/converter.js
This commit is contained in:
16
DocService/npm-shrinkwrap.json
generated
16
DocService/npm-shrinkwrap.json
generated
@ -1651,6 +1651,11 @@
|
||||
"esprima": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
@ -2058,9 +2063,9 @@
|
||||
}
|
||||
},
|
||||
"oracledb": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.8.0.tgz",
|
||||
"integrity": "sha512-A4ds4n4xtjPTzk1gwrHWuMeWsEcrScF0GFgVebGrhNpHSAzn6eDwMKcSbakZODKfFcI099iqhmqWsgko8D+7Ww=="
|
||||
"version": "6.9.0",
|
||||
"resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.9.0.tgz",
|
||||
"integrity": "sha512-NwPbIGPv6m0GTFSbyy4/5WEjsKMiiJRxztLmYUcfD3oyh/uXdmVmKOwEWr84wFwWJ/0wQrYQh4PjnzvShibRaA=="
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.11",
|
||||
@ -2336,11 +2341,6 @@
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
},
|
||||
"require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
"multi-integer-range": "5.2.0",
|
||||
"multiparty": "4.2.3",
|
||||
"mysql2": "3.13.0",
|
||||
"oracledb": "6.8.0",
|
||||
"oracledb": "6.9.0",
|
||||
"pg": "8.14.0",
|
||||
"redis": "4.7.0",
|
||||
"retry": "0.13.1",
|
||||
@ -51,6 +51,9 @@
|
||||
"../Common/sources/storage/storage-s3.js",
|
||||
"../Common/sources/storage/storage-az.js",
|
||||
"../Common/node_modules/axios/dist/node/axios.cjs"
|
||||
],
|
||||
"assets": [
|
||||
"node_modules/oracledb/build/Release/oracledb-*.node"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -538,6 +538,7 @@ function fillJwtByConnection(ctx, conn) {
|
||||
user.id = conn.user.idOriginal;
|
||||
user.name = conn.user.username;
|
||||
user.index = conn.user.indexUser;
|
||||
user.customerId = conn.user.customerId;
|
||||
if (conn.coEditingMode) {
|
||||
edit.coEditing = {mode: conn.coEditingMode};
|
||||
}
|
||||
@ -557,8 +558,8 @@ function sendData(ctx, conn, data) {
|
||||
const type = data ? data.type : null;
|
||||
ctx.logger.debug('sendData: type = %s', type);
|
||||
}
|
||||
function sendDataWarning(ctx, conn, msg) {
|
||||
sendData(ctx, conn, {type: 'warning', message: msg});
|
||||
function sendDataWarning(ctx, conn, code, description) {
|
||||
sendData(ctx, conn, {type: 'warning', code: code, message: description});
|
||||
}
|
||||
function sendDataMessage(ctx, conn, msg) {
|
||||
if (!conn.permissions || false !== conn.permissions.chat) {
|
||||
@ -604,13 +605,10 @@ function sendReleaseLock(ctx, conn, userLocks) {
|
||||
}
|
||||
function modifyConnectionForPassword(ctx, conn, isEnterCorrectPassword) {
|
||||
return co(function* () {
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
if (isEnterCorrectPassword) {
|
||||
conn.isEnterCorrectPassword = true;
|
||||
if (tenTokenEnableBrowser) {
|
||||
const sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
sendDataRefreshToken(ctx, conn, sessionToken);
|
||||
}
|
||||
let sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
sendDataRefreshToken(ctx, conn, sessionToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1183,7 +1181,7 @@ async function saveRelativeFromChanges(ctx, conn, responseKey, data) {
|
||||
let docId = data.docId;
|
||||
const token = data.token;
|
||||
let forceSaveRes;
|
||||
if (tenTokenEnableBrowser) {
|
||||
if (tenTokenEnableBrowser || token) {
|
||||
docId = null;
|
||||
const checkJwtRes = await checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
|
||||
if (checkJwtRes.decoded) {
|
||||
@ -1823,9 +1821,9 @@ exports.install = function (server, callbackFunction) {
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
const handshake = socket.handshake;
|
||||
if (tenTokenEnableBrowser) {
|
||||
const secretType = handshake?.auth?.session ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser;
|
||||
const token = handshake?.auth?.session || handshake?.auth?.token;
|
||||
const token = handshake?.auth?.session || handshake?.auth?.token;
|
||||
if (tenTokenEnableBrowser || token) {
|
||||
let secretType = !!handshake?.auth?.session ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser;
|
||||
checkJwtRes = yield checkJwt(ctx, token, secretType);
|
||||
if (!checkJwtRes.decoded) {
|
||||
res = new Error('not authorized');
|
||||
@ -2033,7 +2031,6 @@ exports.install = function (server, callbackFunction) {
|
||||
* @param reason - the reason of the disconnection (either client or server-side)
|
||||
*/
|
||||
function* closeDocument(ctx, conn, reason) {
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles);
|
||||
|
||||
ctx.logger.info('Connection closed or timed out: reason = %s', reason);
|
||||
@ -2073,10 +2070,8 @@ exports.install = function (server, callbackFunction) {
|
||||
modifyConnectionEditorToView(ctx, conn);
|
||||
conn.isCloseCoAuthoring = true;
|
||||
yield addPresence(ctx, conn, true);
|
||||
if (tenTokenEnableBrowser) {
|
||||
const sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
sendDataRefreshToken(ctx, conn, sessionToken);
|
||||
}
|
||||
let sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
sendDataRefreshToken(ctx, conn, sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2322,8 +2317,6 @@ exports.install = function (server, callbackFunction) {
|
||||
}
|
||||
|
||||
function* sendFileErrorAuth(ctx, conn, sessionId, errorId, code, opt_notWarn) {
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
conn.sessionId = sessionId; //restore old
|
||||
//Kill previous connections
|
||||
connections = _.reject(connections, el => {
|
||||
@ -2337,10 +2330,8 @@ exports.install = function (server, callbackFunction) {
|
||||
// We put it in an array, because we need to send data to open/save the document
|
||||
connections.push(conn);
|
||||
yield addPresence(ctx, conn, true);
|
||||
if (tenTokenEnableBrowser) {
|
||||
const sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
sendDataRefreshToken(ctx, conn, sessionToken);
|
||||
}
|
||||
let sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
sendDataRefreshToken(ctx, conn, sessionToken);
|
||||
sendFileError(ctx, conn, errorId, code, opt_notWarn);
|
||||
}
|
||||
}
|
||||
@ -2542,6 +2533,7 @@ exports.install = function (server, callbackFunction) {
|
||||
if (decoded.userAuth) {
|
||||
data.documentCallbackUrl = JSON.stringify(decoded.userAuth);
|
||||
data.mode = decoded.userAuth.mode;
|
||||
data.forcedViewMode = decoded.userAuth.forcedViewMode;
|
||||
}
|
||||
if (decoded.queryParams) {
|
||||
const queryParams = decoded.queryParams;
|
||||
@ -2702,6 +2694,9 @@ exports.install = function (server, callbackFunction) {
|
||||
//like in Common.Utils.fillUserInfo(web-apps/apps/common/main/lib/util/utils.js)
|
||||
dataUser.username = user.group.toString() + String.fromCharCode(160) + dataUser.username;
|
||||
}
|
||||
if (user.customerId) {
|
||||
dataUser.customerId = user.customerId;
|
||||
}
|
||||
}
|
||||
if (edit.user && edit.user.name) {
|
||||
data.denyChangeName = true;
|
||||
@ -2751,9 +2746,10 @@ exports.install = function (server, callbackFunction) {
|
||||
const [licenseInfo] = yield tenantManager.getTenantLicense(ctx);
|
||||
let isDecoded = false;
|
||||
//check jwt
|
||||
if (tenTokenEnableBrowser) {
|
||||
const token = data.jwtSession || data.jwtOpen;
|
||||
if (tenTokenEnableBrowser || token) {
|
||||
const secretType = data.jwtSession ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser;
|
||||
const checkJwtRes = yield checkJwt(ctx, data.jwtSession || data.jwtOpen, secretType);
|
||||
const checkJwtRes = yield checkJwt(ctx, token, secretType);
|
||||
if (checkJwtRes.decoded) {
|
||||
isDecoded = true;
|
||||
const decoded = checkJwtRes.decoded;
|
||||
@ -2905,6 +2901,7 @@ exports.install = function (server, callbackFunction) {
|
||||
id: curUserId,
|
||||
idOriginal: curUserIdOriginal,
|
||||
username: fillUsername(ctx, data),
|
||||
customerId: user.customerId,
|
||||
indexUser: curIndexUser,
|
||||
view: !isEditMode(data.permissions, data.mode)
|
||||
};
|
||||
@ -3044,6 +3041,8 @@ exports.install = function (server, callbackFunction) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (data.forcedViewMode) {
|
||||
sendDataWarning(ctx, conn, constants.FORCED_VIEW_MODE, 'Forced view mode');
|
||||
}
|
||||
//Set the unique ID
|
||||
if (bIsRestore) {
|
||||
@ -3325,7 +3324,6 @@ exports.install = function (server, callbackFunction) {
|
||||
} while (connections.length > 0 && changes && tenMaxRequestChanges === changes.length);
|
||||
}
|
||||
function* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, opt_hasForgotten, opt_openedAt) {
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize);
|
||||
const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload);
|
||||
|
||||
@ -3341,7 +3339,7 @@ exports.install = function (server, callbackFunction) {
|
||||
let allMessages = yield editorData.getMessages(ctx, docId);
|
||||
allMessages = allMessages.length > 0 ? allMessages : undefined; //todo client side
|
||||
let sessionToken;
|
||||
if (tenTokenEnableBrowser && !bIsRestore) {
|
||||
if (!bIsRestore) {
|
||||
sessionToken = yield fillJwtByConnection(ctx, conn);
|
||||
}
|
||||
const tenEditor = getEditorConfig(ctx);
|
||||
@ -3839,12 +3837,13 @@ exports.install = function (server, callbackFunction) {
|
||||
const c_LR = constants.LICENSE_RESULT;
|
||||
let licenseType = licenseInfo.type;
|
||||
if (c_LR.Success === licenseType || c_LR.SuccessLimit === licenseType) {
|
||||
let notificationLimit;
|
||||
let notificationLimit, notificationLimitTitle;
|
||||
let notificationTemplate = tenNotificationRuleLicenseLimitEdit;
|
||||
let notificationType = notificationTypes.LICENSE_LIMIT_EDIT;
|
||||
let notificationPercent = 100;
|
||||
if (licenseInfo.usersCount) {
|
||||
const nowUTC = getLicenseNowUtc();
|
||||
notificationLimitTitle = 'user';
|
||||
notificationLimit = 'users';
|
||||
if (isLiveViewer) {
|
||||
notificationTemplate = tenNotificationRuleLicenseLimitLiveViewer;
|
||||
@ -3876,6 +3875,7 @@ exports.install = function (server, callbackFunction) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
notificationLimitTitle = 'connection';
|
||||
notificationLimit = 'connections';
|
||||
if (isLiveViewer) {
|
||||
notificationTemplate = tenNotificationRuleLicenseLimitLiveViewer;
|
||||
@ -3899,7 +3899,7 @@ exports.install = function (server, callbackFunction) {
|
||||
}
|
||||
if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || 100 !== notificationPercent) {
|
||||
const applicationName = (process.env.APPLICATION_NAME || '').toUpperCase();
|
||||
const title = util.format(notificationTemplate.title, applicationName);
|
||||
const title = util.format(notificationTemplate.title, applicationName, notificationLimitTitle);
|
||||
const message = util.format(notificationTemplate.body, notificationPercent, notificationLimit);
|
||||
if (100 !== notificationPercent) {
|
||||
ctx.logger.warn(message);
|
||||
@ -3922,7 +3922,6 @@ exports.install = function (server, callbackFunction) {
|
||||
ctx.initFromPubSub(data);
|
||||
yield ctx.initTenantCache();
|
||||
ctx.logger.debug('pubsub message start:%s', msg);
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
let participants;
|
||||
let participant;
|
||||
@ -4053,7 +4052,7 @@ exports.install = function (server, callbackFunction) {
|
||||
case commonDefines.c_oPublishType.warning:
|
||||
participants = getParticipants(data.docId);
|
||||
_.each(participants, participant => {
|
||||
sendDataWarning(ctx, participant, data.description);
|
||||
sendDataWarning(ctx, participant, undefined, data.description);
|
||||
});
|
||||
break;
|
||||
case commonDefines.c_oPublishType.cursor:
|
||||
@ -4105,10 +4104,8 @@ exports.install = function (server, callbackFunction) {
|
||||
ctx.logger.debug('changeConnectionInfo: userId = %s', data.useridoriginal);
|
||||
participant.user.username = cmd.getUserName();
|
||||
yield addPresence(ctx, participant, false);
|
||||
if (tenTokenEnableBrowser) {
|
||||
const sessionToken = yield fillJwtByConnection(ctx, participant);
|
||||
sendDataRefreshToken(ctx, participant, sessionToken);
|
||||
}
|
||||
let sessionToken = yield fillJwtByConnection(ctx, participant);
|
||||
sendDataRefreshToken(ctx, participant, sessionToken);
|
||||
}
|
||||
}
|
||||
if (hasChanges) {
|
||||
|
||||
@ -162,7 +162,6 @@ async function proxyRequest(req, res) {
|
||||
try {
|
||||
ctx.logger.info('Start proxyRequest');
|
||||
await ctx.initTenantCache();
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
const tenAiApiTimeout = ctx.getCfg('aiSettings.timeout', cfgAiApiTimeout);
|
||||
const tenAiApi = ctx.getCfg('aiSettings', cfgAiSettings);
|
||||
const tenAiApiProxy = ctx.getCfg('aiSettings.proxy', cfgAiApiProxy);
|
||||
@ -174,23 +173,26 @@ async function proxyRequest(req, res) {
|
||||
|
||||
let docId = '';
|
||||
let userId = '';
|
||||
if (tenTokenEnableBrowser) {
|
||||
const checkJwtRes = await docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
|
||||
if (!checkJwtRes || checkJwtRes.err) {
|
||||
ctx.logger.error('proxyRequest: checkJwtHeader error: %s', checkJwtRes?.err);
|
||||
res.status(403).json({
|
||||
error: {
|
||||
message: 'proxyRequest: checkJwtHeader error',
|
||||
code: '403'
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
userId = checkJwtRes?.decoded?.editorConfig?.user?.id;
|
||||
docId = checkJwtRes?.decoded?.document?.key;
|
||||
ctx.setDocId(docId);
|
||||
ctx.setUserId(userId);
|
||||
}
|
||||
let userName = '';
|
||||
let userCustomerId = '';
|
||||
let checkJwtRes = await docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
|
||||
if (!checkJwtRes || checkJwtRes.err) {
|
||||
ctx.logger.error('proxyRequest: checkJwtHeader error: %s', checkJwtRes?.err);
|
||||
res.status(403).json({
|
||||
error: {
|
||||
message: 'proxyRequest: checkJwtHeader error',
|
||||
code: '403'
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
docId = checkJwtRes?.decoded?.document?.key;
|
||||
userId = checkJwtRes?.decoded?.editorConfig?.user?.id;
|
||||
userName = checkJwtRes?.decoded?.editorConfig?.user?.name;
|
||||
userCustomerId = checkJwtRes?.decoded?.editorConfig?.user?.customerId;
|
||||
|
||||
ctx.setDocId(docId);
|
||||
ctx.setUserId(userId);
|
||||
}
|
||||
|
||||
if (!tenAiApi?.providers) {
|
||||
@ -250,8 +252,11 @@ async function proxyRequest(req, res) {
|
||||
|
||||
const dataObject = {
|
||||
key: docId,
|
||||
user: userId,
|
||||
customer_id: licenseInfo.customerId
|
||||
user: {
|
||||
id: userId,
|
||||
name: userName,
|
||||
customerId: userCustomerId || licenseInfo.customerId
|
||||
}
|
||||
};
|
||||
|
||||
const secret = await tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox);
|
||||
|
||||
@ -694,6 +694,7 @@ function* commandImgurls(ctx, conn, cmd, outputData) {
|
||||
let isInJwtToken = false;
|
||||
const token = cmd.getTokenDownload();
|
||||
if (tenTokenEnableBrowser && token) {
|
||||
// allow requests without token
|
||||
const checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
|
||||
if (checkJwtRes.decoded) {
|
||||
//todo multiple url case
|
||||
@ -1472,7 +1473,7 @@ exports.downloadAs = function (req, res) {
|
||||
ctx.logger.debug('Start downloadAs: %s', strCmd);
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
if (tenTokenEnableBrowser) {
|
||||
if (tenTokenEnableBrowser || cmd.getTokenDownload() || cmd.getTokenSession()) {
|
||||
let isValidJwt = false;
|
||||
if (cmd.getTokenDownload()) {
|
||||
const checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenDownload(), commonDefines.c_oAscSecretType.Browser);
|
||||
@ -1559,28 +1560,25 @@ exports.saveFile = function (req, res) {
|
||||
docId = cmd.getDocId();
|
||||
ctx.setDocId(docId);
|
||||
ctx.logger.debug('Start saveFile');
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
if (tenTokenEnableBrowser) {
|
||||
let isValidJwt = false;
|
||||
const checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session);
|
||||
if (checkJwtRes.decoded) {
|
||||
const doc = checkJwtRes.decoded.document;
|
||||
const edit = checkJwtRes.decoded.editorConfig;
|
||||
if (doc.ds_encrypted && !edit.ds_view && !edit.ds_isCloseCoAuthoring) {
|
||||
isValidJwt = true;
|
||||
docId = doc.key;
|
||||
cmd.setDocId(doc.key);
|
||||
} else {
|
||||
ctx.logger.warn('Error saveFile jwt: %s', 'access deny');
|
||||
}
|
||||
let isValidJwt = false;
|
||||
let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session);
|
||||
if (checkJwtRes.decoded) {
|
||||
let doc = checkJwtRes.decoded.document;
|
||||
var edit = checkJwtRes.decoded.editorConfig;
|
||||
if (doc.ds_encrypted && !edit.ds_view && !edit.ds_isCloseCoAuthoring) {
|
||||
isValidJwt = true;
|
||||
docId = doc.key;
|
||||
cmd.setDocId(doc.key);
|
||||
} else {
|
||||
ctx.logger.warn('Error saveFile jwt: %s', checkJwtRes.description);
|
||||
}
|
||||
if (!isValidJwt) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
ctx.logger.warn('Error saveFile jwt: %s', 'access deny');
|
||||
}
|
||||
} else {
|
||||
ctx.logger.warn('Error saveFile jwt: %s', checkJwtRes.description);
|
||||
}
|
||||
if (!isValidJwt) {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
ctx.setDocId(docId);
|
||||
cmd.setStatusInfo(constants.NO_ERROR);
|
||||
@ -1605,22 +1603,18 @@ exports.saveFile = function (req, res) {
|
||||
};
|
||||
function getPrintFileUrl(ctx, docId, baseUrl, filename) {
|
||||
return co(function* () {
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
const tenTokenSessionAlgorithm = ctx.getCfg('services.CoAuthoring.token.session.algorithm', cfgTokenSessionAlgorithm);
|
||||
const tenTokenSessionExpires = ms(ctx.getCfg('services.CoAuthoring.token.session.expires', cfgTokenSessionExpires));
|
||||
|
||||
baseUrl = utils.checkBaseUrl(ctx, baseUrl);
|
||||
let token = '';
|
||||
if (tenTokenEnableBrowser) {
|
||||
const payload = {document: {key: docId}};
|
||||
token = yield docsCoServer.signToken(
|
||||
ctx,
|
||||
payload,
|
||||
tenTokenSessionAlgorithm,
|
||||
tenTokenSessionExpires / 1000,
|
||||
commonDefines.c_oAscSecretType.Session
|
||||
);
|
||||
}
|
||||
let payload = {document: {key: docId}};
|
||||
let token = yield docsCoServer.signToken(
|
||||
ctx,
|
||||
payload,
|
||||
tenTokenSessionAlgorithm,
|
||||
tenTokenSessionExpires / 1000,
|
||||
commonDefines.c_oAscSecretType.Session
|
||||
);
|
||||
//while save printed file Chrome's extension seems to rely on the resource name set in the URI https://stackoverflow.com/a/53593453
|
||||
//replace '/' with %2f before encodeURIComponent becase nginx determine %2f as '/' and get wrong system path
|
||||
const userFriendlyName = encodeURIComponent(filename.replace(/\//g, '%2f'));
|
||||
@ -1631,6 +1625,9 @@ function getPrintFileUrl(ctx, docId, baseUrl, filename) {
|
||||
if (ctx.wopiSrc) {
|
||||
res += `&${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(ctx.wopiSrc)}`;
|
||||
}
|
||||
if (ctx.userSessionId) {
|
||||
res += `&${constants.USER_SESSION_ID_NAME}=${encodeURIComponent(ctx.userSessionId)}`;
|
||||
}
|
||||
res += `&filename=${userFriendlyName}`;
|
||||
return res;
|
||||
});
|
||||
@ -1652,29 +1649,26 @@ exports.printFile = function (req, res) {
|
||||
docId = req.params.docid;
|
||||
ctx.setDocId(docId);
|
||||
ctx.logger.info('Start printFile');
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
if (tenTokenEnableBrowser) {
|
||||
const checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session);
|
||||
if (checkJwtRes.decoded) {
|
||||
const docIdBase = checkJwtRes.decoded.document.key;
|
||||
if (!docId.startsWith(docIdBase)) {
|
||||
ctx.logger.warn('Error printFile jwt: description = %s', 'access deny');
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ctx.logger.warn('Error printFile jwt: description = %s', checkJwtRes.description);
|
||||
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session);
|
||||
if (checkJwtRes.decoded) {
|
||||
let docIdBase = checkJwtRes.decoded.document.key;
|
||||
if (!docId.startsWith(docIdBase)) {
|
||||
ctx.logger.warn('Error printFile jwt: description = %s', 'access deny');
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ctx.logger.warn('Error printFile jwt: description = %s', checkJwtRes.description);
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
ctx.setDocId(docId);
|
||||
const streamObj = yield storage.createReadStream(ctx, `${docId}/${constants.OUTPUT_NAME}.pdf`);
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(filename, null, constants.CONTENT_DISPOSITION_INLINE));
|
||||
res.setHeader('Content-Length', streamObj.contentLength);
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
yield utils.pipeStreams(streamObj.readStream, res, true);
|
||||
yield utils.pipeHttpStreams(streamObj.readStream, res);
|
||||
|
||||
if (clientStatsD) {
|
||||
clientStatsD.timing('coauth.printFile', new Date() - startDate);
|
||||
@ -1810,6 +1804,10 @@ exports.downloadFile = function (req, res) {
|
||||
);
|
||||
const response = downloadResult.response;
|
||||
stream = downloadResult.stream;
|
||||
// Sanitize Content-Disposition by removing control chars (prevents CRLF/header injection)
|
||||
if (response.headers['content-disposition']) {
|
||||
response.headers['content-disposition'] = response.headers['content-disposition'].replace(/[\x00-\x1F\x7F]/g, '');
|
||||
}
|
||||
//Set-Cookie resets browser session
|
||||
delete response.headers['set-cookie'];
|
||||
// Set the response headers to match the target response
|
||||
|
||||
@ -33,49 +33,49 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const config = require('config');
|
||||
const co = require('co');
|
||||
var config = require('config');
|
||||
var co = require('co');
|
||||
const mime = require('mime');
|
||||
const taskResult = require('./taskresult');
|
||||
const utils = require('./../../Common/sources/utils');
|
||||
const constants = require('./../../Common/sources/constants');
|
||||
const commonDefines = require('./../../Common/sources/commondefines');
|
||||
const docsCoServer = require('./DocsCoServer');
|
||||
const canvasService = require('./canvasservice');
|
||||
const wopiClient = require('./wopiClient');
|
||||
const storage = require('./../../Common/sources/storage/storage-base');
|
||||
const formatChecker = require('./../../Common/sources/formatchecker');
|
||||
const statsDClient = require('./../../Common/sources/statsdclient');
|
||||
const storageBase = require('./../../Common/sources/storage/storage-base');
|
||||
const operationContext = require('./../../Common/sources/operationContext');
|
||||
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/storage-base');
|
||||
var formatChecker = require('./../../Common/sources/formatchecker');
|
||||
var statsDClient = require('./../../Common/sources/statsdclient');
|
||||
var storageBase = require('./../../Common/sources/storage/storage-base');
|
||||
var operationContext = require('./../../Common/sources/operationContext');
|
||||
const sqlBase = require('./databaseConnectors/baseConnector');
|
||||
const utilsDocService = require('./utilsDocService');
|
||||
const utilsDocService = require("./utilsDocService");
|
||||
|
||||
const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
|
||||
|
||||
const CONVERT_ASYNC_DELAY = 1000;
|
||||
var CONVERT_ASYNC_DELAY = 1000;
|
||||
|
||||
const clientStatsD = statsDClient.getClient();
|
||||
var clientStatsD = statsDClient.getClient();
|
||||
|
||||
function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_checkPassword) {
|
||||
const status = new commonDefines.ConvertStatus(constants.NO_ERROR);
|
||||
var status = new commonDefines.ConvertStatus(constants.NO_ERROR);
|
||||
if (selectRes.length > 0) {
|
||||
const row = selectRes[0];
|
||||
const password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password);
|
||||
var row = selectRes[0];
|
||||
let password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password);
|
||||
switch (row.status) {
|
||||
case commonDefines.FileStatus.Ok:
|
||||
if (password) {
|
||||
let isCorrectPassword;
|
||||
if (encryptedUserPassword) {
|
||||
const decryptedPassword = yield utils.decryptPassword(ctx, password);
|
||||
const userPassword = yield utils.decryptPassword(ctx, encryptedUserPassword);
|
||||
let decryptedPassword = yield utils.decryptPassword(ctx, password);
|
||||
let userPassword = yield utils.decryptPassword(ctx, encryptedUserPassword);
|
||||
isCorrectPassword = decryptedPassword === userPassword;
|
||||
}
|
||||
if (isCorrectPassword) {
|
||||
ctx.logger.debug('getConvertStatus password match');
|
||||
ctx.logger.debug("getConvertStatus password match");
|
||||
status.end = true;
|
||||
} else {
|
||||
ctx.logger.debug('getConvertStatus password mismatch');
|
||||
ctx.logger.debug("getConvertStatus password mismatch");
|
||||
status.err = constants.CONVERT_PASSWORD;
|
||||
}
|
||||
} else {
|
||||
@ -96,7 +96,7 @@ function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_che
|
||||
status.err = constants.UNKNOWN;
|
||||
break;
|
||||
}
|
||||
const lastOpenDate = row.last_open_date;
|
||||
var lastOpenDate = row.last_open_date;
|
||||
if (new Date().getTime() - lastOpenDate.getTime() > utils.getConvertionTimeout(ctx)) {
|
||||
status.err = constants.CONVERT_TIMEOUT;
|
||||
}
|
||||
@ -107,8 +107,8 @@ function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_che
|
||||
}
|
||||
function* getConvertPath(ctx, docId, fileTo, formatTo) {
|
||||
if (constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML === formatTo || constants.AVS_OFFICESTUDIO_FILE_OTHER_ODF === formatTo) {
|
||||
const list = yield storage.listObjects(ctx, docId);
|
||||
const baseName = path.basename(fileTo, path.extname(fileTo));
|
||||
let list = yield storage.listObjects(ctx, docId);
|
||||
let baseName = path.basename(fileTo, path.extname(fileTo));
|
||||
for (let i = 0; i < list.length; ++i) {
|
||||
if (path.basename(list[i], path.extname(list[i])) === baseName) {
|
||||
return list[i];
|
||||
@ -124,8 +124,8 @@ function* getConvertUrl(ctx, baseUrl, fileToPath, title) {
|
||||
return yield storage.getSignedUrl(ctx, baseUrl, fileToPath, commonDefines.c_oAscUrlTypes.Temporary, title);
|
||||
}
|
||||
function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority, opt_expiration, opt_queue, opt_checkPassword) {
|
||||
const docId = cmd.getDocId();
|
||||
let startDate = null;
|
||||
var docId = cmd.getDocId();
|
||||
var startDate = null;
|
||||
if (clientStatsD) {
|
||||
startDate = new Date();
|
||||
}
|
||||
@ -133,7 +133,7 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
|
||||
|
||||
let bCreate = false;
|
||||
if (!opt_taskExist) {
|
||||
const task = new taskResult.TaskResultData();
|
||||
let task = new taskResult.TaskResultData();
|
||||
task.tenant = ctx.tenant;
|
||||
task.key = docId;
|
||||
task.status = commonDefines.FileStatus.WaitQueue;
|
||||
@ -142,34 +142,34 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
|
||||
const upsertRes = yield taskResult.upsert(ctx, task);
|
||||
bCreate = upsertRes.isInsert;
|
||||
}
|
||||
let selectRes;
|
||||
let status;
|
||||
var selectRes;
|
||||
var status;
|
||||
if (!bCreate) {
|
||||
selectRes = yield taskResult.select(ctx, docId);
|
||||
status = yield* getConvertStatus(ctx, cmd.getDocId(), cmd.getPassword(), selectRes, opt_checkPassword);
|
||||
status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword);
|
||||
}
|
||||
if (bCreate || commonDefines.FileStatus.None === selectRes?.[0]?.status) {
|
||||
const queueData = new commonDefines.TaskQueueData();
|
||||
if (bCreate || (commonDefines.FileStatus.None === selectRes?.[0]?.status)) {
|
||||
var queueData = new commonDefines.TaskQueueData();
|
||||
queueData.setCtx(ctx);
|
||||
queueData.setCmd(cmd);
|
||||
if (opt_fileTo) {
|
||||
queueData.setToFile(opt_fileTo);
|
||||
}
|
||||
queueData.setFromOrigin(true);
|
||||
const priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW;
|
||||
var priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW;
|
||||
yield* docsCoServer.addTask(queueData, priority, opt_queue, opt_expiration);
|
||||
status = new commonDefines.ConvertStatus(constants.NO_ERROR);
|
||||
}
|
||||
//wait
|
||||
if (!async) {
|
||||
let waitTime = 0;
|
||||
var waitTime = 0;
|
||||
while (true) {
|
||||
if (status.end || constants.NO_ERROR != status.err) {
|
||||
break;
|
||||
}
|
||||
yield utils.sleep(CONVERT_ASYNC_DELAY);
|
||||
selectRes = yield taskResult.select(ctx, docId);
|
||||
status = yield* getConvertStatus(ctx, cmd.getDocId(), cmd.getPassword(), selectRes, opt_checkPassword);
|
||||
status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword);
|
||||
waitTime += CONVERT_ASYNC_DELAY;
|
||||
if (waitTime > utils.getConvertionTimeout(ctx)) {
|
||||
status.err = constants.CONVERT_TIMEOUT;
|
||||
@ -183,25 +183,10 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
|
||||
return status;
|
||||
}
|
||||
|
||||
async function convertFromChanges(
|
||||
ctx,
|
||||
docId,
|
||||
baseUrl,
|
||||
forceSave,
|
||||
externalChangeInfo,
|
||||
opt_userdata,
|
||||
opt_formdata,
|
||||
opt_userConnectionId,
|
||||
opt_userConnectionDocId,
|
||||
opt_responseKey,
|
||||
opt_priority,
|
||||
opt_expiration,
|
||||
opt_queue,
|
||||
opt_redisKey,
|
||||
opt_initShardKey,
|
||||
opt_jsonParams
|
||||
) {
|
||||
const cmd = new commonDefines.InputCommand();
|
||||
async function convertFromChanges(ctx, docId, baseUrl, forceSave, externalChangeInfo, opt_userdata, opt_formdata,
|
||||
opt_userConnectionId, opt_userConnectionDocId, opt_responseKey, opt_priority,
|
||||
opt_expiration, opt_queue, opt_redisKey, opt_initShardKey, opt_jsonParams) {
|
||||
var cmd = new commonDefines.InputCommand();
|
||||
cmd.setCommand('sfcm');
|
||||
cmd.setDocId(docId);
|
||||
cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML);
|
||||
@ -237,45 +222,45 @@ async function convertFromChanges(
|
||||
cmd.appendJsonParams(opt_jsonParams);
|
||||
}
|
||||
|
||||
const commandSfctByCmdRes = await canvasService.commandSfctByCmd(ctx, cmd, opt_priority, opt_expiration, opt_queue, opt_initShardKey);
|
||||
let commandSfctByCmdRes = await canvasService.commandSfctByCmd(ctx, cmd, opt_priority, opt_expiration, opt_queue, opt_initShardKey);
|
||||
if (!commandSfctByCmdRes) {
|
||||
return new commonDefines.ConvertStatus(constants.UNKNOWN);
|
||||
}
|
||||
let fileTo = constants.OUTPUT_NAME;
|
||||
const outputExt = formatChecker.getStringFromFormat(cmd.getOutputFormat());
|
||||
var fileTo = constants.OUTPUT_NAME;
|
||||
let outputExt = formatChecker.getStringFromFormat(cmd.getOutputFormat());
|
||||
if (outputExt) {
|
||||
fileTo += '.' + outputExt;
|
||||
}
|
||||
const status = await co(convertByCmd(ctx, cmd, true, fileTo, undefined, opt_priority, opt_expiration, opt_queue));
|
||||
let status = await co(convertByCmd(ctx, cmd, true, fileTo, undefined, opt_priority, opt_expiration, opt_queue));
|
||||
if (status.end) {
|
||||
const fileToPath = await co(getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat()));
|
||||
let fileToPath = await co(getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat()));
|
||||
status.setExtName(path.extname(fileToPath));
|
||||
status.setUrl(await co(getConvertUrl(ctx, baseUrl, fileToPath, cmd.getTitle())));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
function parseIntParam(val) {
|
||||
return typeof val === 'string' ? parseInt(val) : val;
|
||||
function parseIntParam(val){
|
||||
return (typeof val === 'string') ? parseInt(val) : val;
|
||||
}
|
||||
|
||||
function convertRequest(req, res, isJson) {
|
||||
return co(function* () {
|
||||
const ctx = new operationContext.Context();
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
yield ctx.initTenantCache();
|
||||
ctx.logger.info('convertRequest start');
|
||||
let params;
|
||||
const authRes = yield docsCoServer.getRequestParams(ctx, req);
|
||||
if (authRes.code === constants.NO_ERROR) {
|
||||
let authRes = yield docsCoServer.getRequestParams(ctx, req);
|
||||
if(authRes.code === constants.NO_ERROR){
|
||||
params = authRes.params;
|
||||
} else {
|
||||
ctx.logger.warn('convertRequest auth failed %j', authRes);
|
||||
utils.fillResponse(req, res, new commonDefines.ConvertStatus(authRes.code), isJson);
|
||||
return;
|
||||
}
|
||||
const filetype = params.filetype || params.fileType || '';
|
||||
const outputtype = params.outputtype || params.outputType || '';
|
||||
let filetype = params.filetype || params.fileType || '';
|
||||
let outputtype = params.outputtype || params.outputType || '';
|
||||
ctx.setDocId(params.key);
|
||||
|
||||
if (params.key && !constants.DOC_ID_REGEX.test(params.key)) {
|
||||
@ -301,21 +286,19 @@ function convertRequest(req, res, isJson) {
|
||||
} else if (false === params.pdf.pdfa && constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === outputFormat) {
|
||||
outputFormat = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF;
|
||||
}
|
||||
if (
|
||||
params.pdf.form &&
|
||||
(constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === outputFormat || constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === outputFormat)
|
||||
) {
|
||||
if (params.pdf.form && (constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === outputFormat ||
|
||||
constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === outputFormat)) {
|
||||
outputFormat = constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF;
|
||||
} else if (false === params.pdf.form) {
|
||||
oformAsPdf = true;
|
||||
}
|
||||
}
|
||||
//todo use hash of params as id
|
||||
const docId = 'conv_' + params.key + '_' + outputFormat;
|
||||
const cmd = new commonDefines.InputCommand();
|
||||
let docId = 'conv_' + params.key + '_' + outputFormat;
|
||||
var cmd = new commonDefines.InputCommand();
|
||||
cmd.setCommand('conv');
|
||||
cmd.setUrl(params.url);
|
||||
cmd.setEmbeddedFonts(false); //params.embeddedfonts'];
|
||||
cmd.setEmbeddedFonts(false);//params.embeddedfonts'];
|
||||
cmd.setFormat(filetype);
|
||||
cmd.setDocId(docId);
|
||||
cmd.setOutputFormat(outputFormat);
|
||||
@ -324,13 +307,12 @@ function convertRequest(req, res, isJson) {
|
||||
|
||||
cmd.setCodepage(commonDefines.c_oAscEncodingsMap[params.codePage] || commonDefines.c_oAscCodePageUtf8);
|
||||
cmd.setDelimiter(parseIntParam(params.delimiter) || commonDefines.c_oAscCsvDelimiter.Comma);
|
||||
if (undefined != params.delimiterChar) {
|
||||
if(undefined != params.delimiterChar)
|
||||
cmd.setDelimiterChar(params.delimiterChar);
|
||||
}
|
||||
if (params.region) {
|
||||
cmd.setLCID(utilsDocService.localeToLCID(params.region));
|
||||
}
|
||||
const jsonParams = {};
|
||||
let jsonParams = {};
|
||||
if (params.documentLayout) {
|
||||
jsonParams['documentLayout'] = params.documentLayout;
|
||||
}
|
||||
@ -349,18 +331,18 @@ function convertRequest(req, res, isJson) {
|
||||
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
|
||||
return;
|
||||
}
|
||||
const encryptedPassword = yield utils.encryptPassword(ctx, params.password);
|
||||
let encryptedPassword = yield utils.encryptPassword(ctx, params.password);
|
||||
cmd.setPassword(encryptedPassword);
|
||||
}
|
||||
if (authRes.isDecoded) {
|
||||
cmd.setWithAuthorization(true);
|
||||
}
|
||||
let thumbnail = params.thumbnail;
|
||||
var thumbnail = params.thumbnail;
|
||||
if (thumbnail) {
|
||||
if (typeof thumbnail === 'string') {
|
||||
thumbnail = JSON.parse(thumbnail);
|
||||
}
|
||||
const thumbnailData = new commonDefines.CThumbnailData(thumbnail);
|
||||
var thumbnailData = new commonDefines.CThumbnailData(thumbnail);
|
||||
//constants from CXIMAGE_FORMAT_
|
||||
switch (cmd.getOutputFormat()) {
|
||||
case constants.AVS_OFFICESTUDIO_FILE_IMAGE_JPG:
|
||||
@ -381,12 +363,12 @@ function convertRequest(req, res, isJson) {
|
||||
outputExt = 'zip';
|
||||
}
|
||||
}
|
||||
let documentRenderer = params.documentRenderer;
|
||||
var documentRenderer = params.documentRenderer;
|
||||
if (documentRenderer) {
|
||||
if (typeof documentRenderer === 'string') {
|
||||
documentRenderer = JSON.parse(documentRenderer);
|
||||
}
|
||||
const textParamsData = new commonDefines.CTextParams();
|
||||
var textParamsData = new commonDefines.CTextParams();
|
||||
switch (documentRenderer.textAssociation) {
|
||||
case 'plainParagraph':
|
||||
textParamsData.setAssociation(3);
|
||||
@ -407,26 +389,23 @@ function convertRequest(req, res, isJson) {
|
||||
if (params.title) {
|
||||
cmd.setTitle(path.basename(params.title, path.extname(params.title)) + '.' + outputExt);
|
||||
}
|
||||
let async = typeof params.async === 'string' ? 'true' == params.async : params.async;
|
||||
var async = (typeof params.async === 'string') ? 'true' == params.async : params.async;
|
||||
if (async && !req.query[constants.SHARD_KEY_API_NAME] && !req.query[constants.SHARD_KEY_WOPI_NAME] && process.env.DEFAULT_SHARD_KEY) {
|
||||
ctx.logger.warn(
|
||||
'convertRequest set async=false. Pass query string parameter "%s" to correctly process request in sharded cluster',
|
||||
constants.SHARD_KEY_API_NAME
|
||||
);
|
||||
ctx.logger.warn('convertRequest set async=false. Pass query string parameter "%s" to correctly process request in sharded cluster', constants.SHARD_KEY_API_NAME);
|
||||
async = false;
|
||||
}
|
||||
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== cmd.getOutputFormat()) {
|
||||
const fileTo = constants.OUTPUT_NAME + '.' + outputExt;
|
||||
const status = yield* convertByCmd(ctx, cmd, async, fileTo, undefined, undefined, undefined, undefined, true);
|
||||
let fileTo = constants.OUTPUT_NAME + '.' + outputExt;
|
||||
var status = yield* convertByCmd(ctx, cmd, async, fileTo, undefined, undefined, undefined, undefined, true);
|
||||
if (status.end) {
|
||||
const fileToPath = yield* getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat());
|
||||
let fileToPath = yield* getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat());
|
||||
status.setExtName(path.extname(fileToPath));
|
||||
status.setUrl(yield* getConvertUrl(ctx, utils.getBaseUrlByRequest(ctx, req), fileToPath, cmd.getTitle()));
|
||||
ctx.logger.debug('convertRequest: url = %s', status.url);
|
||||
}
|
||||
utils.fillResponse(req, res, status, isJson);
|
||||
} else {
|
||||
const addresses = utils.forwarded(req);
|
||||
var addresses = utils.forwarded(req);
|
||||
ctx.logger.warn('Error convert unknown outputtype: query = %j from = %s', params, addresses);
|
||||
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson);
|
||||
}
|
||||
@ -447,28 +426,28 @@ function convertRequestXml(req, res) {
|
||||
|
||||
function builderRequest(req, res) {
|
||||
return co(function* () {
|
||||
const ctx = new operationContext.Context();
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
yield ctx.initTenantCache();
|
||||
ctx.logger.info('builderRequest start');
|
||||
const authRes = yield docsCoServer.getRequestParams(ctx, req);
|
||||
const params = authRes.params;
|
||||
let authRes = yield docsCoServer.getRequestParams(ctx, req);
|
||||
let params = authRes.params;
|
||||
let docId = params.key;
|
||||
ctx.setDocId(docId);
|
||||
|
||||
let error = authRes.code;
|
||||
let urls;
|
||||
let end = false;
|
||||
const needCreateId = !docId;
|
||||
const isInBody = 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) {
|
||||
const task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8);
|
||||
let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8);
|
||||
docId = task.key;
|
||||
ctx.setDocId(docId);
|
||||
}
|
||||
const cmd = new commonDefines.InputCommand();
|
||||
let cmd = new commonDefines.InputCommand();
|
||||
cmd.setCommand('builder');
|
||||
cmd.setBuilderParams({argument: params.argument});
|
||||
if (authRes.isDecoded) {
|
||||
@ -482,31 +461,30 @@ function builderRequest(req, res) {
|
||||
yield storageBase.putObject(ctx, docId + '/script.docbuilder', req.body, req.body.length);
|
||||
}
|
||||
if (needCreateId) {
|
||||
const queueData = new commonDefines.TaskQueueData();
|
||||
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 async = (typeof params.async === 'string') ? 'true' === params.async : params.async;
|
||||
if (async && !req.query[constants.SHARD_KEY_API_NAME] && !req.query[constants.SHARD_KEY_WOPI_NAME] && process.env.DEFAULT_SHARD_KEY) {
|
||||
ctx.logger.warn(
|
||||
'builderRequest set async=false. Pass query string parameter "%s" to correctly process request in sharded cluster',
|
||||
constants.SHARD_KEY_API_NAME
|
||||
);
|
||||
ctx.logger.warn('builderRequest set async=false. Pass query string parameter "%s" to correctly process request in sharded cluster', constants.SHARD_KEY_API_NAME);
|
||||
async = false;
|
||||
}
|
||||
const status = yield* convertByCmd(ctx, cmd, async, undefined, undefined, constants.QUEUE_PRIORITY_LOW);
|
||||
let status = yield* convertByCmd(ctx, cmd, async, undefined, undefined, constants.QUEUE_PRIORITY_LOW);
|
||||
end = status.end;
|
||||
error = status.err;
|
||||
if (end) {
|
||||
urls = yield storageBase.getSignedUrls(ctx, utils.getBaseUrlByRequest(ctx, req), docId + '/output', commonDefines.c_oAscUrlTypes.Temporary);
|
||||
urls = yield storageBase.getSignedUrls(ctx, utils.getBaseUrlByRequest(ctx, req), docId + '/output',
|
||||
commonDefines.c_oAscUrlTypes.Temporary);
|
||||
}
|
||||
} else if (error === constants.NO_ERROR) {
|
||||
error = constants.UNKNOWN;
|
||||
}
|
||||
ctx.logger.debug('End builderRequest request: urls = %j end = %s error = %s', urls, end, error);
|
||||
utils.fillResponseBuilder(res, docId, urls, end, error);
|
||||
} catch (e) {
|
||||
}
|
||||
catch (e) {
|
||||
ctx.logger.error('Error builderRequest: %s', e.stack);
|
||||
utils.fillResponseBuilder(res, undefined, undefined, undefined, constants.UNKNOWN);
|
||||
} finally {
|
||||
@ -515,8 +493,8 @@ function builderRequest(req, res) {
|
||||
});
|
||||
}
|
||||
function convertTo(req, res) {
|
||||
return co(function* () {
|
||||
const ctx = new operationContext.Context();
|
||||
return co(function*() {
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
yield ctx.initTenantCache();
|
||||
@ -526,7 +504,7 @@ function convertTo(req, res) {
|
||||
format = req.params.format;
|
||||
}
|
||||
//todo https://github.com/LibreOffice/core/blob/9d3366f5b392418dc83bc0adbe3d215cff4b3605/desktop/source/lib/init.cxx#L3478
|
||||
const password = req.body['Password'];
|
||||
let password = req.body['Password'];
|
||||
if (password) {
|
||||
if (password.length > constants.PASSWORD_MAX_LENGTH) {
|
||||
ctx.logger.warn('convert-to Password too long actual = %s; max = %s', password.length, constants.PASSWORD_MAX_LENGTH);
|
||||
@ -535,7 +513,7 @@ function convertTo(req, res) {
|
||||
}
|
||||
}
|
||||
//by analogy with Password
|
||||
const passwordToOpen = req.body['PasswordToOpen'];
|
||||
let passwordToOpen = req.body['PasswordToOpen'];
|
||||
if (passwordToOpen) {
|
||||
if (passwordToOpen.length > constants.PASSWORD_MAX_LENGTH) {
|
||||
ctx.logger.warn('convert-to PasswordToOpen too long actual = %s; max = %s', passwordToOpen.length, constants.PASSWORD_MAX_LENGTH);
|
||||
@ -543,13 +521,13 @@ function convertTo(req, res) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const pdfVer = req.body['PDFVer'];
|
||||
if (pdfVer && pdfVer.startsWith('PDF/A') && 'pdf' === format) {
|
||||
let pdfVer = req.body['PDFVer'];
|
||||
if (pdfVer && pdfVer.startsWith("PDF/A") && 'pdf' === format) {
|
||||
format = 'pdfa';
|
||||
}
|
||||
const fullSheetPreview = req.body['FullSheetPreview'];
|
||||
const lang = req.body['lang'];
|
||||
const outputFormat = formatChecker.getFormatFromString(format);
|
||||
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);
|
||||
@ -559,22 +537,22 @@ function convertTo(req, res) {
|
||||
if (req.files?.length > 0 && req.files[0].originalname && req.files[0].buffer) {
|
||||
const file = req.files[0];
|
||||
originalname = file.originalname;
|
||||
const filetype = path.extname(file.originalname).substring(1);
|
||||
let filetype = path.extname(file.originalname).substring(1);
|
||||
if (filetype && !constants.EXTENTION_REGEX.test(filetype)) {
|
||||
ctx.logger.warn('convertRequest unexpected filetype = %s', filetype);
|
||||
res.sendStatus(400);
|
||||
return;
|
||||
}
|
||||
|
||||
const task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
|
||||
let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
|
||||
docId = task.key;
|
||||
ctx.setDocId(docId);
|
||||
|
||||
//todo stream
|
||||
const buffer = file.buffer;
|
||||
let buffer = file.buffer;
|
||||
yield storageBase.putObject(ctx, docId + '/origin.' + filetype, buffer, buffer.length);
|
||||
|
||||
const cmd = new commonDefines.InputCommand();
|
||||
let cmd = new commonDefines.InputCommand();
|
||||
cmd.setCommand('conv');
|
||||
cmd.setDocId(docId);
|
||||
cmd.setFormat(filetype);
|
||||
@ -585,55 +563,51 @@ function convertTo(req, res) {
|
||||
cmd.setLCID(utilsDocService.localeToLCID(lang));
|
||||
}
|
||||
if (fullSheetPreview) {
|
||||
cmd.appendJsonParams({
|
||||
spreadsheetLayout: {
|
||||
ignorePrintArea: true,
|
||||
fitToWidth: 1,
|
||||
fitToHeight: 1
|
||||
}
|
||||
});
|
||||
cmd.appendJsonParams({'spreadsheetLayout': {
|
||||
"ignorePrintArea": true,
|
||||
"fitToWidth": 1,
|
||||
"fitToHeight": 1
|
||||
}});
|
||||
} else {
|
||||
cmd.appendJsonParams({
|
||||
spreadsheetLayout: {
|
||||
ignorePrintArea: true,
|
||||
fitToWidth: 0,
|
||||
fitToHeight: 0,
|
||||
scale: 100
|
||||
}
|
||||
});
|
||||
cmd.appendJsonParams({'spreadsheetLayout': {
|
||||
"ignorePrintArea": true,
|
||||
"fitToWidth": 0,
|
||||
"fitToHeight": 0,
|
||||
"scale": 100
|
||||
}});
|
||||
}
|
||||
if (password) {
|
||||
const encryptedPassword = yield utils.encryptPassword(ctx, password);
|
||||
let encryptedPassword = yield utils.encryptPassword(ctx, password);
|
||||
cmd.setSavePassword(encryptedPassword);
|
||||
}
|
||||
if (passwordToOpen) {
|
||||
const encryptedPassword = yield utils.encryptPassword(ctx, passwordToOpen);
|
||||
let encryptedPassword = yield utils.encryptPassword(ctx, passwordToOpen);
|
||||
cmd.setPassword(encryptedPassword);
|
||||
}
|
||||
|
||||
fileTo = constants.OUTPUT_NAME;
|
||||
const outputExt = formatChecker.getStringFromFormat(outputFormat);
|
||||
let outputExt = formatChecker.getStringFromFormat(outputFormat);
|
||||
if (outputExt) {
|
||||
fileTo += '.' + outputExt;
|
||||
}
|
||||
|
||||
const queueData = new commonDefines.TaskQueueData();
|
||||
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);
|
||||
|
||||
const async = false;
|
||||
let async = false;
|
||||
status = yield* convertByCmd(ctx, cmd, async, fileTo);
|
||||
}
|
||||
if (status && status.end && constants.NO_ERROR === status.err) {
|
||||
const filename = path.basename(originalname, path.extname(originalname)) + path.extname(fileTo);
|
||||
const streamObj = yield storage.createReadStream(ctx, `${docId}/${fileTo}`);
|
||||
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);
|
||||
yield utils.pipeHttpStreams(streamObj.readStream, res);
|
||||
} else {
|
||||
ctx.logger.error('convert-to error status:%j', status);
|
||||
res.sendStatus(400);
|
||||
@ -647,19 +621,19 @@ function convertTo(req, res) {
|
||||
});
|
||||
}
|
||||
function convertAndEdit(ctx, wopiParams, filetypeFrom, filetypeTo) {
|
||||
return co(function* () {
|
||||
return co(function*() {
|
||||
try {
|
||||
ctx.logger.info('convert-and-edit start');
|
||||
|
||||
const task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
|
||||
const docId = task.key;
|
||||
const outputFormat = formatChecker.getFormatFromString(filetypeTo);
|
||||
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;
|
||||
}
|
||||
|
||||
const cmd = new commonDefines.InputCommand();
|
||||
let cmd = new commonDefines.InputCommand();
|
||||
cmd.setCommand('conv');
|
||||
cmd.setDocId(docId);
|
||||
cmd.setUrl('dummy-url');
|
||||
@ -668,18 +642,18 @@ function convertAndEdit(ctx, wopiParams, filetypeFrom, filetypeTo) {
|
||||
cmd.setOutputFormat(outputFormat);
|
||||
|
||||
let fileTo = constants.OUTPUT_NAME;
|
||||
const outputExt = formatChecker.getStringFromFormat(outputFormat);
|
||||
let outputExt = formatChecker.getStringFromFormat(outputFormat);
|
||||
if (outputExt) {
|
||||
fileTo += '.' + outputExt;
|
||||
}
|
||||
|
||||
const queueData = new commonDefines.TaskQueueData();
|
||||
let queueData = new commonDefines.TaskQueueData();
|
||||
queueData.setCtx(ctx);
|
||||
queueData.setCmd(cmd);
|
||||
queueData.setToFile(fileTo);
|
||||
yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW);
|
||||
|
||||
const async = true;
|
||||
let async = true;
|
||||
yield* convertByCmd(ctx, cmd, async, fileTo);
|
||||
return docId;
|
||||
} catch (err) {
|
||||
@ -690,37 +664,29 @@ function convertAndEdit(ctx, wopiParams, filetypeFrom, filetypeTo) {
|
||||
});
|
||||
}
|
||||
function getConverterHtmlHandler(req, res) {
|
||||
return co(function* () {
|
||||
const isJson = true;
|
||||
const ctx = new operationContext.Context();
|
||||
return co(function*() {
|
||||
let isJson = true;
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
yield ctx.initTenantCache();
|
||||
ctx.logger.info('convert-and-edit-handler start');
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
const wopiSrc = req.query['wopisrc'];
|
||||
const access_token = req.query['access_token'];
|
||||
const targetext = req.query['targetext'];
|
||||
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
|
||||
);
|
||||
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;
|
||||
}
|
||||
const token = req.query['token'];
|
||||
if (tenTokenEnableBrowser) {
|
||||
const checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
|
||||
let token = req.query['token'];
|
||||
if (tenTokenEnableBrowser || token) {
|
||||
let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
|
||||
if (checkJwtRes.decoded) {
|
||||
docId = checkJwtRes.decoded.docId;
|
||||
} else {
|
||||
@ -731,24 +697,14 @@ function getConverterHtmlHandler(req, res) {
|
||||
}
|
||||
ctx.setDocId(docId);
|
||||
|
||||
const selectRes = yield taskResult.select(ctx, docId);
|
||||
const status = yield* getConvertStatus(ctx, docId, undefined, selectRes);
|
||||
let selectRes = yield taskResult.select(ctx, docId);
|
||||
let status = yield* getConvertStatus(ctx, docId, undefined, selectRes);
|
||||
if (status.end && constants.NO_ERROR === status.err) {
|
||||
const fileTo = `${docId}/${constants.OUTPUT_NAME}.${targetext}`;
|
||||
let fileTo = `${docId}/${constants.OUTPUT_NAME}.${targetext}`;
|
||||
|
||||
const metadata = yield storage.headObject(ctx, fileTo);
|
||||
const streamObj = yield storage.createReadStream(ctx, fileTo);
|
||||
const putRelativeRes = yield wopiClient.putRelativeFile(
|
||||
ctx,
|
||||
wopiSrc,
|
||||
access_token,
|
||||
null,
|
||||
streamObj.readStream,
|
||||
metadata.ContentLength,
|
||||
`.${targetext}`,
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
let metadata = yield storage.headObject(ctx, fileTo);
|
||||
let streamObj = yield storage.createReadStream(ctx, fileTo);
|
||||
let putRelativeRes = yield wopiClient.putRelativeFile(ctx, wopiSrc, access_token, null, streamObj.readStream, metadata.ContentLength, `.${targetext}`, undefined, true);
|
||||
if (putRelativeRes) {
|
||||
status.setUrl(putRelativeRes.HostEditUrl);
|
||||
status.setExtName('.' + targetext);
|
||||
|
||||
@ -36,12 +36,18 @@ const oracledb = require('oracledb');
|
||||
const config = require('config');
|
||||
const connectorUtilities = require('./connectorUtilities');
|
||||
const utils = require('../../../Common/sources/utils');
|
||||
const operationContext = require('../../../Common/sources/operationContext');
|
||||
|
||||
const configSql = config.get('services.CoAuthoring.sql');
|
||||
const cfgTableResult = configSql.get('tableResult');
|
||||
const cfgTableChanges = configSql.get('tableChanges');
|
||||
const cfgMaxPacketSize = configSql.get('max_allowed_packet');
|
||||
|
||||
// Limit rows per executeMany call to avoid large internal batches causing server instability
|
||||
// Especially important for 21c 21.3 with NCLOB columns and batched inserts
|
||||
// Higher to reduce round-trips while still bounded by cfgMaxPacketSize
|
||||
const MAX_EXECUTE_MANY_ROWS = 2000;
|
||||
|
||||
const connectionConfiguration = {
|
||||
user: configSql.get('dbUser'),
|
||||
password: configSql.get('dbPass'),
|
||||
@ -50,6 +56,18 @@ const connectionConfiguration = {
|
||||
poolMax: configSql.get('connectionlimit')
|
||||
};
|
||||
const additionalOptions = config.util.cloneDeep(configSql.get('oracleExtraOptions'));
|
||||
// Initialize thick mode
|
||||
if (additionalOptions?.thin === false) {
|
||||
try {
|
||||
oracledb.initOracleClient(additionalOptions?.libDir ? {libDir: additionalOptions.libDir} : {});
|
||||
} catch (err) {
|
||||
operationContext.global.logger.error('Failed to initialize thick Oracle client:', err);
|
||||
}
|
||||
}
|
||||
// Remove Oracle client options before creating connection config
|
||||
delete additionalOptions.thin;
|
||||
delete additionalOptions.libDir;
|
||||
|
||||
const configuration = Object.assign({}, connectionConfiguration, additionalOptions);
|
||||
const forceClosingCountdownMs = 2000;
|
||||
let pool = null;
|
||||
@ -130,7 +148,20 @@ async function executeQuery(ctx, sqlCommand, values = [], noModifyRes = false, n
|
||||
}
|
||||
}
|
||||
|
||||
async function executeBunch(ctx, sqlCommand, values = [], noLog = false) {
|
||||
/**
|
||||
* Execute a batched DML statement using executeMany with optional bind options.
|
||||
* Notes:
|
||||
* - Accepts array-of-arrays for positional binds (recommended for :0..:N placeholders)
|
||||
* - Options can include bindDefs, batchErrors, autoCommit, etc.
|
||||
* - Logs batchErrors summary when present to aid debugging while keeping normal return shape
|
||||
* @param {object} ctx - request context with logger
|
||||
* @param {string} sqlCommand - SQL text with positional bind placeholders
|
||||
* @param {Array<Array<any>>|Array<object>} values - rows to bind
|
||||
* @param {object} [options] - executeMany options (e.g., { bindDefs: [...], batchErrors: true })
|
||||
* @param {boolean} [noLog=false] - disable error logging
|
||||
* @returns {{affectedRows:number}} affected rows count aggregate
|
||||
*/
|
||||
async function executeBunch(ctx, sqlCommand, values = [], options, noLog = false) {
|
||||
let connection = null;
|
||||
try {
|
||||
if (!pool) {
|
||||
@ -139,17 +170,36 @@ async function executeBunch(ctx, sqlCommand, values = [], noLog = false) {
|
||||
|
||||
connection = await pool.getConnection();
|
||||
|
||||
const result = await connection.executeMany(sqlCommand, values);
|
||||
const result = await connection.executeMany(sqlCommand, values, options);
|
||||
|
||||
// Log batch errors if requested, without changing the public return shape
|
||||
if (options?.batchErrors && Array.isArray(result?.batchErrors) && result.batchErrors.length && !noLog) {
|
||||
const allDup = result.batchErrors.every(e => e?.errorNum === 1); // ORA-00001
|
||||
const logMessage = `executeMany() batchErrors for: ${sqlCommand} -> count=${result.batchErrors.length}${allDup ? ' (duplicates)' : ''}`;
|
||||
if (allDup) {
|
||||
ctx.logger.debug(logMessage);
|
||||
} else {
|
||||
ctx.logger.error(logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return {affectedRows: result?.rowsAffected ?? 0};
|
||||
} catch (error) {
|
||||
if (!noLog) {
|
||||
ctx.logger.error(`sqlQuery() error while executing query: ${sqlCommand}\n${error.stack}`);
|
||||
ctx.logger.error(`executeBunch() error while executing query: ${sqlCommand}\n${error.stack}`);
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
connection?.close();
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.close();
|
||||
} catch (error) {
|
||||
if (!noLog) {
|
||||
ctx.logger.error(`connection.close() error while executing batched query: ${sqlCommand}\n${error.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,7 +338,20 @@ function insertChanges(ctx, tableChanges, startIndex, objChanges, docId, index,
|
||||
);
|
||||
}
|
||||
|
||||
async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, docId, index, user) {
|
||||
/**
|
||||
* Insert a sequence of change records into the doc_changes table using executeMany.
|
||||
* Removes APPEND_VALUES hint, adds explicit bindDefs, and chunks batches to reduce risk of ORA-03106.
|
||||
* @param {object} ctx - request context
|
||||
* @param {string} tableChanges - table name
|
||||
* @param {number} startIndex - start offset in objChanges
|
||||
* @param {Array<{change:string,time:Date|number|string}>} objChanges - changes payload
|
||||
* @param {string} docId - document id
|
||||
* @param {number} index - starting change_id value
|
||||
* @param {{id:string,idOriginal:string,username:string}} user - user info
|
||||
* @param {boolean} [allowParallel=true] - allow one-level parallel execution for next chunk
|
||||
* @returns {Promise<{affectedRows:number}>}
|
||||
*/
|
||||
async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, docId, index, user, allowParallel = true) {
|
||||
if (startIndex === objChanges.length) {
|
||||
return {affectedRows: 0};
|
||||
}
|
||||
@ -296,14 +359,15 @@ async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, doc
|
||||
const parametersCount = 8;
|
||||
const maxPlaceholderLength = ':99'.length;
|
||||
// (parametersCount - 1) - separator symbols length.
|
||||
const maxInsertStatementLength =
|
||||
`INSERT /*+ APPEND_VALUES*/INTO ${tableChanges} VALUES()`.length + maxPlaceholderLength * parametersCount + (parametersCount - 1);
|
||||
const maxInsertStatementLength = `INSERT INTO ${tableChanges} VALUES()`.length + maxPlaceholderLength * parametersCount + (parametersCount - 1);
|
||||
let packetCapacityReached = false;
|
||||
|
||||
const values = [];
|
||||
const indexBytes = 4;
|
||||
const timeBytes = 8;
|
||||
let lengthUtf8Current = 0;
|
||||
// Track the longest change_data length in this batch to choose efficient bind type
|
||||
let maxChangeLen = 0;
|
||||
let currentIndex = startIndex;
|
||||
for (; currentIndex < objChanges.length; ++currentIndex, ++index) {
|
||||
// 4 bytes is maximum for utf8 symbol.
|
||||
@ -314,41 +378,70 @@ async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, doc
|
||||
4 *
|
||||
(ctx.tenant.length + docId.length + user.id.length + user.idOriginal.length + user.username.length + objChanges[currentIndex].change.length);
|
||||
|
||||
if (lengthUtf8Row + lengthUtf8Current >= cfgMaxPacketSize && currentIndex > startIndex) {
|
||||
// Chunk by packet size and by max rows per batch
|
||||
if ((lengthUtf8Row + lengthUtf8Current >= cfgMaxPacketSize || values.length >= MAX_EXECUTE_MANY_ROWS) && currentIndex > startIndex) {
|
||||
packetCapacityReached = true;
|
||||
break;
|
||||
}
|
||||
|
||||
const parameters = [
|
||||
ctx.tenant,
|
||||
docId,
|
||||
index,
|
||||
user.id,
|
||||
user.idOriginal,
|
||||
user.username,
|
||||
objChanges[currentIndex].change,
|
||||
objChanges[currentIndex].time
|
||||
];
|
||||
// Ensure TIMESTAMP bind is a valid JS Date
|
||||
const _t = objChanges[currentIndex].time;
|
||||
const changeTime = _t instanceof Date ? _t : new Date(_t);
|
||||
const changeStr = objChanges[currentIndex].change;
|
||||
if (changeStr.length > maxChangeLen) maxChangeLen = changeStr.length;
|
||||
|
||||
const rowValues = {...parameters};
|
||||
const parameters = [ctx.tenant, docId, index, user.id, user.idOriginal, user.username, changeStr, changeTime];
|
||||
|
||||
values.push(rowValues);
|
||||
// Use positional binding (array-of-arrays) for :0..:7 placeholders
|
||||
values.push(parameters);
|
||||
lengthUtf8Current += lengthUtf8Row;
|
||||
}
|
||||
|
||||
const placeholder = [];
|
||||
for (let i = 0; i < parametersCount; i++) {
|
||||
for (let i = 1; i <= parametersCount; i++) {
|
||||
placeholder.push(`:${i}`);
|
||||
}
|
||||
|
||||
const sqlInsert = `INSERT /*+ APPEND_VALUES*/INTO ${tableChanges} VALUES(${placeholder.join(',')})`;
|
||||
const result = await executeBunch(ctx, sqlInsert, values);
|
||||
// Use IGNORE_ROW_ON_DUPKEY_INDEX to avoid duplicate-key errors on retries and speed up inserts
|
||||
const sqlInsert = `INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(${tableChanges}, DOC_CHANGES_UNIQUE) */ INTO ${tableChanges} VALUES(${placeholder.join(',')})`;
|
||||
|
||||
// Explicit bind definitions to avoid thin-driver type inference pitfalls on NVARCHAR2/NCLOB/TIMESTAMP
|
||||
const bindDefs = [
|
||||
{type: oracledb.DB_TYPE_NVARCHAR, maxSize: 255}, // tenant NVARCHAR2(255)
|
||||
{type: oracledb.DB_TYPE_NVARCHAR, maxSize: 255}, // id NVARCHAR2(255)
|
||||
{type: oracledb.DB_TYPE_NUMBER}, // change_id NUMBER
|
||||
{type: oracledb.DB_TYPE_NVARCHAR, maxSize: 255}, // user_id NVARCHAR2(255)
|
||||
{type: oracledb.DB_TYPE_NVARCHAR, maxSize: 255}, // user_id_original NVARCHAR2(255)
|
||||
{type: oracledb.DB_TYPE_NVARCHAR, maxSize: 255}, // user_name NVARCHAR2(255)
|
||||
// Prefer NVARCHAR2 for small payloads to avoid expensive NCLOB handling; fallback to NCLOB when needed
|
||||
maxChangeLen <= 2000
|
||||
? {type: oracledb.DB_TYPE_NVARCHAR, maxSize: Math.max(16, Math.min(maxChangeLen || 16, 2000))}
|
||||
: {type: oracledb.DB_TYPE_NCLOB}, // change_data
|
||||
{type: oracledb.DB_TYPE_TIMESTAMP} // change_date TIMESTAMP
|
||||
];
|
||||
|
||||
// With IGNORE_ROW_ON_DUPKEY_INDEX, duplicates are skipped server-side; disable batchErrors to reduce overhead
|
||||
const executeOptions = {bindDefs, batchErrors: false, autoCommit: true};
|
||||
|
||||
// Execute current batch and optionally process next chunk concurrently if allowed
|
||||
const p1 = executeBunch(ctx, sqlInsert, values, executeOptions);
|
||||
|
||||
if (packetCapacityReached) {
|
||||
const recursiveValue = await insertChangesAsync(ctx, tableChanges, currentIndex, objChanges, docId, index, user);
|
||||
result.affectedRows += recursiveValue.affectedRows;
|
||||
if (allowParallel) {
|
||||
// Start processing the remaining chunks concurrently (single-level parallelism)
|
||||
const p2 = insertChangesAsync(ctx, tableChanges, currentIndex, objChanges, docId, index, user, false);
|
||||
const [r1, r2] = await Promise.all([p1, p2]);
|
||||
r1.affectedRows += r2.affectedRows;
|
||||
return r1;
|
||||
}
|
||||
// Parallelism not allowed: finish this batch, then continue sequentially
|
||||
const r1 = await p1;
|
||||
const r2 = await insertChangesAsync(ctx, tableChanges, currentIndex, objChanges, docId, index, user, false);
|
||||
r1.affectedRows += r2.affectedRows;
|
||||
return r1;
|
||||
}
|
||||
|
||||
const result = await p1;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -32,16 +32,17 @@
|
||||
|
||||
'use strict';
|
||||
const crypto = require('crypto');
|
||||
const co = require('co');
|
||||
var multiparty = require('multiparty');
|
||||
var co = require('co');
|
||||
const utilsDocService = require('./utilsDocService');
|
||||
const docsCoServer = require('./DocsCoServer');
|
||||
const utils = require('./../../Common/sources/utils');
|
||||
const constants = require('./../../Common/sources/constants');
|
||||
const storageBase = require('./../../Common/sources/storage/storage-base');
|
||||
const formatChecker = require('./../../Common/sources/formatchecker');
|
||||
var docsCoServer = require('./DocsCoServer');
|
||||
var utils = require('./../../Common/sources/utils');
|
||||
var constants = require('./../../Common/sources/constants');
|
||||
var storageBase = require('./../../Common/sources/storage/storage-base');
|
||||
var formatChecker = require('./../../Common/sources/formatchecker');
|
||||
const commonDefines = require('./../../Common/sources/commondefines');
|
||||
const operationContext = require('./../../Common/sources/operationContext');
|
||||
const config = require('config');
|
||||
var config = require('config');
|
||||
|
||||
const cfgImageSize = config.get('services.CoAuthoring.server.limits_image_size');
|
||||
const cfgTypesUpload = config.get('services.CoAuthoring.utils.limits_image_types_upload');
|
||||
@ -49,15 +50,11 @@ const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.brow
|
||||
|
||||
const PATTERN_ENCRYPTED = 'ENCRYPTED;';
|
||||
|
||||
// function* checkJwtUpload(ctx, errorName, token){
|
||||
// const checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session);
|
||||
// return checkJwtUploadTransformRes(ctx, errorName, checkJwtRes);
|
||||
// }
|
||||
function checkJwtUploadTransformRes(ctx, errorName, checkJwtRes) {
|
||||
const res = {err: true, docId: null, userid: null, encrypted: null};
|
||||
function checkJwtUploadTransformRes(ctx, errorName, checkJwtRes){
|
||||
var res = {err: true, docId: null, userid: null, encrypted: null};
|
||||
if (checkJwtRes.decoded) {
|
||||
const doc = checkJwtRes.decoded.document;
|
||||
const edit = checkJwtRes.decoded.editorConfig;
|
||||
var doc = checkJwtRes.decoded.document;
|
||||
var edit = checkJwtRes.decoded.editorConfig;
|
||||
//todo check view and pdf editor (temporary fix)
|
||||
if (!edit.ds_isCloseCoAuthoring) {
|
||||
res.err = false;
|
||||
@ -74,12 +71,12 @@ function checkJwtUploadTransformRes(ctx, errorName, checkJwtRes) {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
exports.uploadImageFile = function (req, res) {
|
||||
exports.uploadImageFile = function(req, res) {
|
||||
return co(function* () {
|
||||
let httpStatus = 200;
|
||||
let docId = 'null';
|
||||
const output = {};
|
||||
const ctx = new operationContext.Context();
|
||||
var docId = 'null';
|
||||
let output = {};
|
||||
let ctx = new operationContext.Context();
|
||||
try {
|
||||
ctx.initFromRequest(req);
|
||||
yield ctx.initTenantCache();
|
||||
@ -89,35 +86,32 @@ exports.uploadImageFile = function (req, res) {
|
||||
ctx.logger.debug('Start uploadImageFile');
|
||||
const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize);
|
||||
const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload);
|
||||
const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser);
|
||||
|
||||
if (tenTokenEnableBrowser) {
|
||||
let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
|
||||
if (!checkJwtRes) {
|
||||
//todo remove compatibility with previous versions
|
||||
checkJwtRes = yield docsCoServer.checkJwt(ctx, req.query['token'], commonDefines.c_oAscSecretType.Session);
|
||||
}
|
||||
const transformedRes = checkJwtUploadTransformRes(ctx, 'uploadImageFile', checkJwtRes);
|
||||
if (!transformedRes.err) {
|
||||
docId = transformedRes.docId || docId;
|
||||
encrypted = transformedRes.encrypted;
|
||||
ctx.setDocId(docId);
|
||||
ctx.setUserId(transformedRes.userid);
|
||||
} else {
|
||||
httpStatus = 403;
|
||||
}
|
||||
let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session);
|
||||
if (!checkJwtRes) {
|
||||
//todo remove compatibility with previous versions
|
||||
checkJwtRes = yield docsCoServer.checkJwt(ctx, req.query['token'], commonDefines.c_oAscSecretType.Session);
|
||||
}
|
||||
let transformedRes = checkJwtUploadTransformRes(ctx, 'uploadImageFile', checkJwtRes);
|
||||
if (!transformedRes.err) {
|
||||
docId = transformedRes.docId || docId;
|
||||
encrypted = transformedRes.encrypted;
|
||||
ctx.setDocId(docId);
|
||||
ctx.setUserId(transformedRes.userid);
|
||||
} else {
|
||||
httpStatus = 403;
|
||||
}
|
||||
|
||||
if (200 === httpStatus && docId && req.body && Buffer.isBuffer(req.body)) {
|
||||
let buffer = req.body;
|
||||
if (buffer.length <= tenImageSize) {
|
||||
let format = formatChecker.getImageFormat(ctx, buffer);
|
||||
let formatStr = formatChecker.getStringFromFormat(format);
|
||||
var format = formatChecker.getImageFormat(ctx, buffer);
|
||||
var formatStr = formatChecker.getStringFromFormat(format);
|
||||
if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) {
|
||||
formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length));
|
||||
}
|
||||
const supportedFormats = tenTypesUpload || 'jpg';
|
||||
const formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
|
||||
var supportedFormats = tenTypesUpload || 'jpg';
|
||||
let formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr);
|
||||
if (formatLimit) {
|
||||
if (format === constants.AVS_OFFICESTUDIO_FILE_IMAGE_TIFF) {
|
||||
buffer = yield utilsDocService.convertImageToPng(ctx, buffer);
|
||||
@ -125,19 +119,15 @@ exports.uploadImageFile = function (req, res) {
|
||||
formatStr = formatChecker.getStringFromFormat(format);
|
||||
}
|
||||
//a hash is written at the beginning to avoid errors during parallel upload in co-editing
|
||||
const strImageName = crypto.randomBytes(16).toString('hex');
|
||||
const strPathRel = 'media/' + strImageName + '.' + formatStr;
|
||||
const strPath = docId + '/' + strPathRel;
|
||||
var strImageName = crypto.randomBytes(16).toString("hex");
|
||||
var strPathRel = 'media/' + strImageName + '.' + formatStr;
|
||||
var strPath = docId + '/' + strPathRel;
|
||||
|
||||
buffer = yield utilsDocService.fixImageExifRotation(ctx, buffer);
|
||||
|
||||
yield storageBase.putObject(ctx, strPath, buffer, buffer.length);
|
||||
output[strPathRel] = yield storageBase.getSignedUrl(
|
||||
ctx,
|
||||
utils.getBaseUrlByRequest(ctx, req),
|
||||
strPath,
|
||||
commonDefines.c_oAscUrlTypes.Session
|
||||
);
|
||||
output[strPathRel] = yield storageBase.getSignedUrl(ctx, utils.getBaseUrlByRequest(ctx, req), strPath,
|
||||
commonDefines.c_oAscUrlTypes.Session);
|
||||
} else {
|
||||
httpStatus = 415;
|
||||
ctx.logger.debug('uploadImageFile format is not supported');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -106,9 +106,8 @@ async function fillStandardHeaders(ctx, headers, url, access_token) {
|
||||
}
|
||||
headers['X-WOPI-TimeStamp'] = timeStamp;
|
||||
headers['X-WOPI-ClientVersion'] = commonDefines.buildVersion + '.' + commonDefines.buildNumber;
|
||||
// todo
|
||||
// headers['X-WOPI-CorrelationId '] = "";
|
||||
// headers['X-WOPI-SessionId'] = "";
|
||||
headers['X-WOPI-CorrelationId'] = crypto.randomUUID();
|
||||
headers['X-WOPI-SessionId'] = ctx.userSessionId;
|
||||
//remove redundant header https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/common-headers#request-headers
|
||||
// headers['Authorization'] = `Bearer ${access_token}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user