773 lines
30 KiB
JavaScript
773 lines
30 KiB
JavaScript
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2024
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const path = require('path');
|
|
const config = require('config');
|
|
const 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');
|
|
const sqlBase = require('./databaseConnectors/baseConnector');
|
|
const utilsDocService = require('./utilsDocService');
|
|
|
|
const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
|
|
|
|
const CONVERT_ASYNC_DELAY = 1000;
|
|
|
|
const clientStatsD = statsDClient.getClient();
|
|
|
|
function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_checkPassword) {
|
|
const 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);
|
|
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);
|
|
isCorrectPassword = decryptedPassword === userPassword;
|
|
}
|
|
if (isCorrectPassword) {
|
|
ctx.logger.debug('getConvertStatus password match');
|
|
status.end = true;
|
|
} else {
|
|
ctx.logger.debug('getConvertStatus password mismatch');
|
|
status.err = constants.CONVERT_PASSWORD;
|
|
}
|
|
} else {
|
|
status.end = true;
|
|
}
|
|
break;
|
|
case commonDefines.FileStatus.Err:
|
|
status.err = row.status_info;
|
|
break;
|
|
case commonDefines.FileStatus.ErrToReload:
|
|
case commonDefines.FileStatus.NeedPassword:
|
|
status.err = row.status_info;
|
|
yield canvasService.cleanupErrToReload(ctx, docId);
|
|
break;
|
|
case commonDefines.FileStatus.NeedParams:
|
|
case commonDefines.FileStatus.SaveVersion:
|
|
case commonDefines.FileStatus.UpdateVersion:
|
|
status.err = constants.UNKNOWN;
|
|
break;
|
|
}
|
|
const lastOpenDate = row.last_open_date;
|
|
if (new Date().getTime() - lastOpenDate.getTime() > utils.getConvertionTimeout(ctx)) {
|
|
status.err = constants.CONVERT_TIMEOUT;
|
|
}
|
|
} else {
|
|
status.err = constants.UNKNOWN;
|
|
}
|
|
return status;
|
|
}
|
|
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));
|
|
for (let i = 0; i < list.length; ++i) {
|
|
if (path.basename(list[i], path.extname(list[i])) === baseName) {
|
|
return list[i];
|
|
}
|
|
}
|
|
}
|
|
return docId + '/' + fileTo;
|
|
}
|
|
function* getConvertUrl(ctx, baseUrl, fileToPath, title) {
|
|
if (title) {
|
|
title = path.basename(title, path.extname(title)) + path.extname(fileToPath);
|
|
}
|
|
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;
|
|
if (clientStatsD) {
|
|
startDate = new Date();
|
|
}
|
|
ctx.logger.debug('Start convert request');
|
|
|
|
let bCreate = false;
|
|
if (!opt_taskExist) {
|
|
const task = new taskResult.TaskResultData();
|
|
task.tenant = ctx.tenant;
|
|
task.key = docId;
|
|
task.status = commonDefines.FileStatus.WaitQueue;
|
|
task.statusInfo = Math.floor(Date.now() / 60000); //minutes
|
|
|
|
const upsertRes = yield taskResult.upsert(ctx, task);
|
|
bCreate = upsertRes.isInsert;
|
|
}
|
|
let selectRes;
|
|
let status;
|
|
if (!bCreate) {
|
|
selectRes = yield taskResult.select(ctx, docId);
|
|
status = yield* getConvertStatus(ctx, cmd.getDocId(), cmd.getPassword(), selectRes, opt_checkPassword);
|
|
}
|
|
if (bCreate || commonDefines.FileStatus.None === selectRes?.[0]?.status) {
|
|
const 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;
|
|
yield* docsCoServer.addTask(queueData, priority, opt_queue, opt_expiration);
|
|
status = new commonDefines.ConvertStatus(constants.NO_ERROR);
|
|
}
|
|
//wait
|
|
if (!async) {
|
|
let 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);
|
|
waitTime += CONVERT_ASYNC_DELAY;
|
|
if (waitTime > utils.getConvertionTimeout(ctx)) {
|
|
status.err = constants.CONVERT_TIMEOUT;
|
|
}
|
|
}
|
|
}
|
|
ctx.logger.debug('End convert request end %s status %s', status.end, status.err);
|
|
if (clientStatsD) {
|
|
clientStatsD.timing('coauth.convertservice', new Date() - startDate);
|
|
}
|
|
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();
|
|
cmd.setCommand('sfcm');
|
|
cmd.setDocId(docId);
|
|
cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML);
|
|
cmd.setEmbeddedFonts(false);
|
|
cmd.setCodepage(commonDefines.c_oAscCodePageUtf8);
|
|
cmd.setDelimiter(commonDefines.c_oAscCsvDelimiter.Comma);
|
|
cmd.setForceSave(forceSave);
|
|
cmd.setExternalChangeInfo(externalChangeInfo);
|
|
if (externalChangeInfo.lang) {
|
|
//todo lang and region are different
|
|
cmd.setLCID(utilsDocService.localeToLCID(externalChangeInfo.lang));
|
|
}
|
|
if (opt_userdata) {
|
|
cmd.setUserData(opt_userdata);
|
|
}
|
|
if (opt_formdata) {
|
|
//todo put file to storage
|
|
cmd.setFormData(opt_formdata);
|
|
}
|
|
if (opt_userConnectionId) {
|
|
cmd.setUserConnectionId(opt_userConnectionId);
|
|
}
|
|
if (opt_userConnectionDocId) {
|
|
cmd.setUserConnectionDocId(opt_userConnectionDocId);
|
|
}
|
|
if (opt_responseKey) {
|
|
cmd.setResponseKey(opt_responseKey);
|
|
}
|
|
if (opt_redisKey) {
|
|
cmd.setRedisKey(opt_redisKey);
|
|
}
|
|
if (opt_jsonParams) {
|
|
cmd.appendJsonParams(opt_jsonParams);
|
|
}
|
|
|
|
const 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());
|
|
if (outputExt) {
|
|
fileTo += '.' + outputExt;
|
|
}
|
|
const 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()));
|
|
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 convertRequest(req, res, isJson) {
|
|
return co(function* () {
|
|
const 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) {
|
|
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 || '';
|
|
ctx.setDocId(params.key);
|
|
|
|
if (params.key && !constants.DOC_ID_REGEX.test(params.key)) {
|
|
ctx.logger.warn('convertRequest unexpected key = %s', params.key);
|
|
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
|
|
return;
|
|
}
|
|
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;
|
|
}
|
|
let outputFormat = formatChecker.getFormatFromString(outputtype);
|
|
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) {
|
|
ctx.logger.warn('convertRequest unexpected outputtype = %s', outputtype);
|
|
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
|
|
return;
|
|
}
|
|
let oformAsPdf;
|
|
if (params.pdf) {
|
|
if (true === params.pdf.pdfa && constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === outputFormat) {
|
|
outputFormat = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA;
|
|
} 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)
|
|
) {
|
|
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();
|
|
cmd.setCommand('conv');
|
|
cmd.setUrl(params.url);
|
|
cmd.setEmbeddedFonts(false); //params.embeddedfonts'];
|
|
cmd.setFormat(filetype);
|
|
cmd.setDocId(docId);
|
|
cmd.setOutputFormat(outputFormat);
|
|
cmd.setOformAsPdf(oformAsPdf);
|
|
let outputExt = formatChecker.getStringFromFormat(cmd.getOutputFormat());
|
|
|
|
cmd.setCodepage(commonDefines.c_oAscEncodingsMap[params.codePage] || commonDefines.c_oAscCodePageUtf8);
|
|
cmd.setDelimiter(parseIntParam(params.delimiter) || commonDefines.c_oAscCsvDelimiter.Comma);
|
|
if (undefined != params.delimiterChar) cmd.setDelimiterChar(params.delimiterChar);
|
|
if (params.region) {
|
|
cmd.setLCID(utilsDocService.localeToLCID(params.region));
|
|
}
|
|
const jsonParams = {};
|
|
if (params.documentLayout) {
|
|
jsonParams['documentLayout'] = params.documentLayout;
|
|
}
|
|
if (params.spreadsheetLayout) {
|
|
jsonParams['spreadsheetLayout'] = params.spreadsheetLayout;
|
|
}
|
|
if (params.watermark) {
|
|
jsonParams['watermark'] = params.watermark;
|
|
}
|
|
if (Object.keys(jsonParams).length > 0) {
|
|
cmd.appendJsonParams(jsonParams);
|
|
}
|
|
if (params.password) {
|
|
if (params.password.length > constants.PASSWORD_MAX_LENGTH) {
|
|
ctx.logger.warn('convertRequest password too long actual = %s; max = %s', params.password.length, constants.PASSWORD_MAX_LENGTH);
|
|
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
|
|
return;
|
|
}
|
|
const encryptedPassword = yield utils.encryptPassword(ctx, params.password);
|
|
cmd.setPassword(encryptedPassword);
|
|
}
|
|
if (authRes.isDecoded) {
|
|
cmd.setWithAuthorization(true);
|
|
}
|
|
let thumbnail = params.thumbnail;
|
|
if (thumbnail) {
|
|
if (typeof thumbnail === 'string') {
|
|
thumbnail = JSON.parse(thumbnail);
|
|
}
|
|
const thumbnailData = new commonDefines.CThumbnailData(thumbnail);
|
|
//constants from CXIMAGE_FORMAT_
|
|
switch (cmd.getOutputFormat()) {
|
|
case constants.AVS_OFFICESTUDIO_FILE_IMAGE_JPG:
|
|
thumbnailData.setFormat(3);
|
|
break;
|
|
case constants.AVS_OFFICESTUDIO_FILE_IMAGE_PNG:
|
|
thumbnailData.setFormat(4);
|
|
break;
|
|
case constants.AVS_OFFICESTUDIO_FILE_IMAGE_GIF:
|
|
thumbnailData.setFormat(2);
|
|
break;
|
|
case constants.AVS_OFFICESTUDIO_FILE_IMAGE_BMP:
|
|
thumbnailData.setFormat(1);
|
|
break;
|
|
}
|
|
cmd.setThumbnail(thumbnailData);
|
|
if (false === thumbnailData.getFirst() && 0 !== (constants.AVS_OFFICESTUDIO_FILE_IMAGE & cmd.getOutputFormat())) {
|
|
outputExt = 'zip';
|
|
}
|
|
}
|
|
let documentRenderer = params.documentRenderer;
|
|
if (documentRenderer) {
|
|
if (typeof documentRenderer === 'string') {
|
|
documentRenderer = JSON.parse(documentRenderer);
|
|
}
|
|
const textParamsData = new commonDefines.CTextParams();
|
|
switch (documentRenderer.textAssociation) {
|
|
case 'plainParagraph':
|
|
textParamsData.setAssociation(3);
|
|
break;
|
|
case 'plainLine':
|
|
textParamsData.setAssociation(2);
|
|
break;
|
|
case 'blockLine':
|
|
textParamsData.setAssociation(1);
|
|
break;
|
|
case 'blockChar':
|
|
default:
|
|
textParamsData.setAssociation(0);
|
|
break;
|
|
}
|
|
cmd.setTextParams(textParamsData);
|
|
}
|
|
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;
|
|
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
|
|
);
|
|
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);
|
|
if (status.end) {
|
|
const 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);
|
|
ctx.logger.warn('Error convert unknown outputtype: query = %j from = %s', params, addresses);
|
|
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson);
|
|
}
|
|
} catch (e) {
|
|
ctx.logger.error('convertRequest error: %s', e.stack);
|
|
utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson);
|
|
} finally {
|
|
ctx.logger.info('convertRequest end');
|
|
}
|
|
});
|
|
}
|
|
function convertRequestJson(req, res) {
|
|
return convertRequest(req, res, true);
|
|
}
|
|
function convertRequestXml(req, res) {
|
|
return convertRequest(req, res, false);
|
|
}
|
|
|
|
function builderRequest(req, res) {
|
|
return co(function* () {
|
|
const 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 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;
|
|
if (error === constants.NO_ERROR && (params.key || params.url || isInBody)) {
|
|
if (needCreateId) {
|
|
const task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8);
|
|
docId = task.key;
|
|
ctx.setDocId(docId);
|
|
}
|
|
const cmd = new commonDefines.InputCommand();
|
|
cmd.setCommand('builder');
|
|
cmd.setBuilderParams({argument: params.argument});
|
|
if (authRes.isDecoded) {
|
|
cmd.setWithAuthorization(true);
|
|
}
|
|
cmd.setDocId(docId);
|
|
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) {
|
|
const 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;
|
|
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
|
|
);
|
|
async = false;
|
|
}
|
|
const 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);
|
|
}
|
|
} 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) {
|
|
ctx.logger.error('Error builderRequest: %s', e.stack);
|
|
utils.fillResponseBuilder(res, undefined, undefined, undefined, constants.UNKNOWN);
|
|
} finally {
|
|
ctx.logger.info('builderRequest end');
|
|
}
|
|
});
|
|
}
|
|
function convertTo(req, res) {
|
|
return co(function* () {
|
|
const ctx = new operationContext.Context();
|
|
try {
|
|
ctx.initFromRequest(req);
|
|
yield ctx.initTenantCache();
|
|
ctx.logger.info('convert-to start');
|
|
let format = req.body['format'];
|
|
if (req.params.format) {
|
|
format = req.params.format;
|
|
}
|
|
//todo https://github.com/LibreOffice/core/blob/9d3366f5b392418dc83bc0adbe3d215cff4b3605/desktop/source/lib/init.cxx#L3478
|
|
const 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);
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
}
|
|
//by analogy with Password
|
|
const 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);
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
}
|
|
const 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);
|
|
if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) {
|
|
ctx.logger.warn('convert-to unexpected format = %s', format);
|
|
res.sendStatus(400);
|
|
return;
|
|
}
|
|
let docId, fileTo, status, originalname;
|
|
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);
|
|
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);
|
|
docId = task.key;
|
|
ctx.setDocId(docId);
|
|
|
|
//todo stream
|
|
const buffer = file.buffer;
|
|
yield storageBase.putObject(ctx, docId + '/origin.' + filetype, buffer, buffer.length);
|
|
|
|
const cmd = new commonDefines.InputCommand();
|
|
cmd.setCommand('conv');
|
|
cmd.setDocId(docId);
|
|
cmd.setFormat(filetype);
|
|
cmd.setOutputFormat(outputFormat);
|
|
cmd.setCodepage(commonDefines.c_oAscCodePageUtf8);
|
|
cmd.setDelimiter(commonDefines.c_oAscCsvDelimiter.Comma);
|
|
if (lang) {
|
|
cmd.setLCID(utilsDocService.localeToLCID(lang));
|
|
}
|
|
if (fullSheetPreview) {
|
|
cmd.appendJsonParams({
|
|
spreadsheetLayout: {
|
|
ignorePrintArea: true,
|
|
fitToWidth: 1,
|
|
fitToHeight: 1
|
|
}
|
|
});
|
|
} else {
|
|
cmd.appendJsonParams({
|
|
spreadsheetLayout: {
|
|
ignorePrintArea: true,
|
|
fitToWidth: 0,
|
|
fitToHeight: 0,
|
|
scale: 100
|
|
}
|
|
});
|
|
}
|
|
if (password) {
|
|
const encryptedPassword = yield utils.encryptPassword(ctx, password);
|
|
cmd.setSavePassword(encryptedPassword);
|
|
}
|
|
if (passwordToOpen) {
|
|
const encryptedPassword = yield utils.encryptPassword(ctx, passwordToOpen);
|
|
cmd.setPassword(encryptedPassword);
|
|
}
|
|
|
|
fileTo = constants.OUTPUT_NAME;
|
|
const outputExt = formatChecker.getStringFromFormat(outputFormat);
|
|
if (outputExt) {
|
|
fileTo += '.' + outputExt;
|
|
}
|
|
|
|
const 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;
|
|
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}`);
|
|
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.pipeHttpStreams(streamObj.readStream, res);
|
|
} 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');
|
|
|
|
const task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
|
|
const docId = task.key;
|
|
const 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();
|
|
cmd.setCommand('conv');
|
|
cmd.setDocId(docId);
|
|
cmd.setUrl('dummy-url');
|
|
cmd.setWopiParams(wopiParams);
|
|
cmd.setFormat(filetypeFrom);
|
|
cmd.setOutputFormat(outputFormat);
|
|
|
|
let fileTo = constants.OUTPUT_NAME;
|
|
const outputExt = formatChecker.getStringFromFormat(outputFormat);
|
|
if (outputExt) {
|
|
fileTo += '.' + outputExt;
|
|
}
|
|
|
|
const queueData = new commonDefines.TaskQueueData();
|
|
queueData.setCtx(ctx);
|
|
queueData.setCmd(cmd);
|
|
queueData.setToFile(fileTo);
|
|
yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW);
|
|
|
|
const 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* () {
|
|
const isJson = true;
|
|
const 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 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;
|
|
}
|
|
const token = req.query['token'];
|
|
if (tenTokenEnableBrowser || token) {
|
|
const 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);
|
|
|
|
const selectRes = yield taskResult.select(ctx, docId);
|
|
const status = yield* getConvertStatus(ctx, docId, undefined, selectRes);
|
|
if (status.end && constants.NO_ERROR === status.err) {
|
|
const 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
|
|
);
|
|
if (putRelativeRes) {
|
|
status.setUrl(putRelativeRes.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;
|