diff --git a/Common/config/default.json b/Common/config/default.json
index 3be64ae5..0b588e01 100644
--- a/Common/config/default.json
+++ b/Common/config/default.json
@@ -81,7 +81,8 @@
"favIconUrlCell" : "/web-apps/apps/spreadsheeteditor/main/resources/img/favicon.ico",
"favIconUrlSlide" : "/web-apps/apps/presentationeditor/main/resources/img/favicon.ico",
"fileInfoBlockList" : ["FileUrl"],
- "wordView": ["pdf", "djvu", "xps", "oxps", "doc", "dotx", "dotm", "dot", "fodt", "ott", "rtf", "mht", "html", "htm", "xml", "epub", "fb2"],
+ "pdfView": ["pdf", "djvu", "xps", "oxps"],
+ "wordView": ["doc", "dotx", "dotm", "dot", "fodt", "ott", "rtf", "mht", "html", "htm", "xml", "epub", "fb2"],
"wordEdit": ["docx", "docm", "docxf", "oform", "odt", "txt"],
"cellView": ["xls", "xlsb", "xltx", "xltm", "xlt", "fods", "ots"],
"cellEdit": ["xlsx", "xlsm", "ods", "csv"],
diff --git a/DocService/sources/converterservice.js b/DocService/sources/converterservice.js
index 1d8642be..08f07f7a 100644
--- a/DocService/sources/converterservice.js
+++ b/DocService/sources/converterservice.js
@@ -43,6 +43,7 @@ var constants = require('./../../Common/sources/constants');
var commonDefines = require('./../../Common/sources/commondefines');
var docsCoServer = require('./DocsCoServer');
var canvasService = require('./canvasservice');
+var wopiClient = require('./wopiClient');
var storage = require('./../../Common/sources/storage-base');
var formatChecker = require('./../../Common/sources/formatchecker');
var statsDClient = require('./../../Common/sources/statsdclient');
@@ -50,20 +51,20 @@ var storageBase = require('./../../Common/sources/storage-base');
var operationContext = require('./../../Common/sources/operationContext');
const sqlBase = require('./baseConnector');
+const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser');
+
var CONVERT_ASYNC_DELAY = 1000;
var clientStatsD = statsDClient.getClient();
-function* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword) {
+function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_checkPassword) {
var status = new commonDefines.ConvertStatus(constants.NO_ERROR);
if (selectRes.length > 0) {
- var docId = cmd.getDocId();
var row = selectRes[0];
let password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password);
switch (row.status) {
case taskResult.FileStatus.Ok:
if (password) {
- let encryptedUserPassword = cmd.getPassword();
let isCorrectPassword;
if (encryptedUserPassword) {
let decryptedPassword = yield utils.decryptPassword(password);
@@ -147,7 +148,7 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
var status;
if (!bCreate) {
selectRes = yield taskResult.select(ctx, docId);
- status = yield* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword);
+ status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword);
} else {
var queueData = new commonDefines.TaskQueueData();
queueData.setCtx(ctx);
@@ -169,7 +170,7 @@ function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority,
}
yield utils.sleep(CONVERT_ASYNC_DELAY);
selectRes = yield taskResult.select(ctx, docId);
- status = yield* getConvertStatus(ctx, cmd, selectRes, opt_checkPassword);
+ status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword);
waitTime += CONVERT_ASYNC_DELAY;
if (waitTime > utils.CONVERTION_TIMEOUT) {
status.err = constants.CONVERT_TIMEOUT;
@@ -519,8 +520,110 @@ function convertTo(req, res) {
}
});
}
+function convertAndEdit(ctx, wopiParams, filetypeFrom, filetypeTo) {
+ return co(function*() {
+ try {
+ ctx.logger.info('convert-and-edit start');
+
+ let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8);
+ let docId = task.key;
+ let outputFormat = formatChecker.getFormatFromString(filetypeTo);
+ if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) {
+ ctx.logger.debug('convert-and-edit unknown outputFormat %s', filetypeTo);
+ return;
+ }
+
+ let cmd = new commonDefines.InputCommand();
+ cmd.setCommand('conv');
+ cmd.setDocId(docId);
+ cmd.setUrl('dummy-url');
+ cmd.setWopiParams(wopiParams);
+ cmd.setFormat(filetypeFrom);
+ cmd.setOutputFormat(outputFormat);
+
+ let fileTo = constants.OUTPUT_NAME;
+ let outputExt = formatChecker.getStringFromFormat(outputFormat);
+ if (outputExt) {
+ fileTo += '.' + outputExt;
+ }
+
+ let queueData = new commonDefines.TaskQueueData();
+ queueData.setCtx(ctx);
+ queueData.setCmd(cmd);
+ queueData.setToFile(fileTo);
+ yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW);
+
+ let async = true;
+ yield* convertByCmd(ctx, cmd, async, fileTo);
+ return docId;
+ } catch (err) {
+ ctx.logger.error('convert-and-edit error:%s', err.stack);
+ } finally {
+ ctx.logger.info('convert-and-edit end');
+ }
+ });
+}
+function getConverterHtmlHandler(req, res) {
+ return co(function*() {
+ let isJson = true;
+ let ctx = new operationContext.Context();
+ try {
+ ctx.initFromRequest(req);
+ ctx.logger.info('convert-and-edit-handler start');
+
+ let wopiSrc = req.query['wopisrc'];
+ let access_token = req.query['access_token'];
+ let targetext = req.query['targetext'];
+ let docId = req.query['docid'];
+ ctx.setDocId(docId);
+ if (!(wopiSrc && access_token && access_token && targetext && docId) ||
+ constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === formatChecker.getFormatFromString(targetext)) {
+ ctx.logger.debug('convert-and-edit-handler invalid params: wopiSrc=%s; access_token=%s; targetext=%s; docId=%s', wopiSrc, access_token, targetext, docId);
+ utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson);
+ return;
+ }
+ let token = req.query['token'];
+ if (cfgTokenEnableBrowser) {
+ let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser);
+ if (checkJwtRes.decoded) {
+ docId = checkJwtRes.decoded.docId;
+ } else {
+ ctx.logger.debug('convert-and-edit-handler invalid token %j', token);
+ utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.VKEY), isJson);
+ return;
+ }
+ }
+ ctx.setDocId(docId);
+
+ let selectRes = yield taskResult.select(ctx, docId);
+ let status = yield* getConvertStatus(ctx, docId, undefined, selectRes);
+ if (status.end && constants.NO_ERROR === status.err) {
+ let fileTo = `${docId}/${constants.OUTPUT_NAME}.${targetext}`;
+
+ let metadata = yield storage.headObject(ctx, fileTo);
+ let streamObj = yield storage.createReadStream(ctx, fileTo);
+ let postRes = yield wopiClient.putRelativeFile(ctx, wopiSrc, access_token, null, streamObj.readStream, metadata.ContentLength, `.${targetext}`, true);
+ if (postRes) {
+ let fileInfo = JSON.parse(postRes.body);
+ status.setUrl(fileInfo.HostEditUrl);
+ status.setExtName('.' + targetext);
+ } else {
+ status.err = constants.UNKNOWN;
+ }
+ }
+ utils.fillResponse(req, res, status, isJson);
+ } catch (err) {
+ ctx.logger.error('convert-and-edit-handler error:%s', err.stack);
+ utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson);
+ } finally {
+ ctx.logger.info('convert-and-edit-handler end');
+ }
+ });
+}
exports.convertFromChanges = convertFromChanges;
exports.convertJson = convertRequestJson;
exports.convertXml = convertRequestXml;
exports.convertTo = convertTo;
+exports.convertAndEdit = convertAndEdit;
+exports.getConverterHtmlHandler = getConverterHtmlHandler;
exports.builder = builderRequest;
diff --git a/DocService/sources/server.js b/DocService/sources/server.js
index 3f0f4342..92fba29a 100644
--- a/DocService/sources/server.js
+++ b/DocService/sources/server.js
@@ -251,6 +251,8 @@ docsCoServer.install(server, () => {
app.post('/lool/convert-to/:format?', utils.checkClientIp, urleEcodedParser, fileForms.single('data'), converterService.convertTo);
app.post('/cool/convert-to/:format?', utils.checkClientIp, urleEcodedParser, fileForms.single('data'), converterService.convertTo);
app.post('/hosting/wopi/:documentType/:mode', urleEcodedParser, forms.none(), utils.lowercaseQueryString, wopiClient.getEditorHtml);
+ app.post('/hosting/wopi/convert-and-edit/:ext/:targetext', urleEcodedParser, forms.none(), utils.lowercaseQueryString, wopiClient.getConverterHtml);
+ app.get('/hosting/wopi/convert-and-edit-handler', utils.lowercaseQueryString, converterService.getConverterHtmlHandler);
}
app.post('/dummyCallback', utils.checkClientIp, rawFileParser, function(req, res){
diff --git a/DocService/sources/wopiClient.js b/DocService/sources/wopiClient.js
index eb50f079..437c40d6 100644
--- a/DocService/sources/wopiClient.js
+++ b/DocService/sources/wopiClient.js
@@ -49,6 +49,7 @@ const tenantManager = require('./../../Common/sources/tenantManager');
const sqlBase = require('./baseConnector');
const taskResult = require('./taskresult');
const canvasService = require('./canvasservice');
+const converterService = require('./converterservice');
const cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm');
const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires');
@@ -57,6 +58,7 @@ const cfgCallbackRequestTimeout = config.get('services.CoAuthoring.server.callba
const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout');
const cfgWopiFileInfoBlockList = config.get('wopi.fileInfoBlockList');
const cfgWopiWopiZone = config.get('wopi.wopiZone');
+const cfgWopiPdfView = config.get('wopi.pdfView');
const cfgWopiWordView = config.get('wopi.wordView');
const cfgWopiWordEdit = config.get('wopi.wordEdit');
const cfgWopiCellView = config.get('wopi.cellView');
@@ -105,8 +107,12 @@ function discovery(req, res) {
let baseUrl = cfgWopiHost || utils.getBaseUrlByRequest(req);
let names = ['Word','Excel','PowerPoint'];
let favIconUrls = [cfgWopiFavIconUrlWord, cfgWopiFavIconUrlCell, cfgWopiFavIconUrlSlide];
- let exts = [{view: cfgWopiWordView, edit: cfgWopiWordEdit}, {view: cfgWopiCellView, edit: cfgWopiCellEdit},
- {view: cfgWopiSlideView, edit: cfgWopiSlideEdit}];
+ let exts = [
+ {targetext: 'docx', view: cfgWopiPdfView.concat(cfgWopiWordView), edit: cfgWopiWordEdit},
+ {targetext: 'xlsx', view: cfgWopiCellView, edit: cfgWopiCellEdit},
+ {targetext: 'pptx', view: cfgWopiSlideView, edit: cfgWopiSlideEdit}
+ ];
+
let templateStart = `${baseUrl}/hosting/wopi`;
let templateEnd = `&<rs=DC_LLCC&><dchat=DISABLE_CHAT&><embed=EMBEDDED&>`;
templateEnd += `<fs=FULLSCREEN&><hid=HOST_SESSION_ID&><rec=RECORDING&>`;
@@ -129,6 +135,10 @@ function discovery(req, res) {
for (let j = 0; j < ext.view.length; ++j) {
output += ``;
output += ``;
+ if (-1 === cfgWopiPdfView.indexOf(ext.view[j])) {
+ let urlConvert = `${templateStart}/convert-and-edit/${ext.view[j]}/${ext.targetext}?${templateEnd}`;
+ output += ``;
+ }
}
for (let j = 0; j < ext.edit.length; ++j) {
output += ``;
@@ -155,6 +165,10 @@ function discovery(req, res) {
output += ``;
output += ``;
output += ``;
+ if (-1 === cfgWopiPdfView.indexOf(ext.view[j])) {
+ let urlConvert = `${templateStart}/convert-and-edit/${ext.view[j]}/${ext.targetext}?${templateEnd}`;
+ output += ``;
+ }
output += ``;
});
}
@@ -328,9 +342,8 @@ function getEditorHtml(req, res) {
let access_token = req.body['access_token'] || "";
let access_token_ttl = parseInt(req.body['access_token_ttl']) || 0;
- let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`;
- let fileInfo = params.fileInfo = yield checkFileInfo(ctx, uri, access_token, sc);
+ let fileInfo = params.fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token, sc);
if (!fileInfo) {
params.fileInfo = {};
return;
@@ -394,7 +407,7 @@ function getEditorHtml(req, res) {
if (cfgTokenEnableBrowser) {
let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires};
- let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Inbox);
+ let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
params.token = jwt.sign(params, secret, options);
}
} catch (err) {
@@ -412,6 +425,64 @@ function getEditorHtml(req, res) {
}
});
}
+function getConverterHtml(req, res) {
+ return co(function*() {
+ let params = {statusHandler: undefined};
+ let ctx = new operationContext.Context();
+ try {
+ ctx.initFromRequest(req);
+ let wopiSrc = req.query['wopisrc'];
+ let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1);
+ ctx.setDocId(fileId);
+ ctx.logger.info('convert-and-edit start');
+
+ let access_token = req.body['access_token'] || "";
+ let access_token_ttl = parseInt(req.body['access_token_ttl']) || 0;
+ let ext = req.params.ext;
+ let targetext = req.params.targetext;
+
+ if (!(wopiSrc && access_token && access_token_ttl && ext && targetext)) {
+ ctx.logger.debug('convert-and-edit invalid params: wopiSrc=%s; access_token=%s; access_token_ttl=%s; ext=%s; targetext=%s', wopiSrc, access_token, access_token_ttl, ext, targetext);
+ return;
+ }
+
+ let fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token);
+ if (!fileInfo) {
+ ctx.logger.info('convert-and-edit checkFileInfo error');
+ return;
+ }
+
+ let wopiParams = getWopiParams(null, fileInfo, wopiSrc, access_token, access_token_ttl);
+
+ let docId = yield converterService.convertAndEdit(ctx, wopiParams, ext, targetext);
+ if (docId) {
+ let baseUrl = cfgWopiHost || utils.getBaseUrlByRequest(req);
+ params.statusHandler = `${baseUrl}/hosting/wopi/convert-and-edit-handler`;
+ params.statusHandler += `?wopiSrc=${encodeURI(wopiSrc)}&access_token=${encodeURI(access_token)}`;
+ params.statusHandler += `&targetext=${encodeURI(targetext)}&docId=${encodeURI(docId)}`;
+ if (cfgTokenEnableBrowser) {
+ let tokenData = {docId: docId};
+ let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires};
+ let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser);
+ let token = jwt.sign(tokenData, secret, options);
+
+ params.statusHandler += `&token=${encodeURI(token)}`;
+ }
+ }
+ } catch (err) {
+ ctx.logger.error('convert-and-edit error:%s', err.stack);
+ } finally {
+ ctx.logger.debug('convert-and-edit render params=%j', params);
+ try {
+ res.render("convert-and-edit-wopi", params);
+ } catch (err) {
+ ctx.logger.error('convert-and-edit error:%s', err.stack);
+ res.sendStatus(400);
+ }
+ ctx.logger.info('convert-and-edit end');
+ }
+ });
+}
function putFile(ctx, wopiParams, data, dataStream, dataSize, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) {
return co(function* () {
let postRes = null;
@@ -457,6 +528,34 @@ function putFile(ctx, wopiParams, data, dataStream, dataSize, userLastChangeId,
return postRes;
});
}
+function putRelativeFile(ctx, wopiSrc, access_token, data, dataStream, dataSize, suggestedTarget, isFileConversion) {
+ return co(function* () {
+ let postRes = null;
+ try {
+ ctx.logger.info('wopi putRelativeFile start');
+
+ let uri = `${wopiSrc}?access_token=${access_token}`;
+ let filterStatus = yield checkIpFilter(ctx, uri);
+ if (0 !== filterStatus) {
+ return postRes;
+ }
+
+ let headers = {'X-WOPI-Override': 'PUT_RELATIVE', 'X-WOPI-SuggestedTarget': utf7.encode(suggestedTarget),
+ 'X-WOPI-FileConversion': isFileConversion};
+ fillStandardHeaders(headers, uri, access_token);
+
+ ctx.logger.debug('wopi putRelativeFile request uri=%s headers=%j', uri, headers);
+ postRes = yield utils.postRequestPromise(uri, data, dataStream, dataSize, cfgCallbackRequestTimeout, undefined, headers);
+ ctx.logger.debug('wopi putRelativeFile response headers=%j', postRes.response.headers);
+ ctx.logger.debug('wopi putRelativeFile response body:%s', postRes.body);
+ } catch (err) {
+ ctx.logger.error('wopi error putRelativeFile:%s', err.stack);
+ } finally {
+ ctx.logger.info('wopi putRelativeFile end');
+ }
+ return postRes;
+ });
+}
function renameFile(ctx, wopiParams, name) {
return co(function* () {
let res = undefined;
@@ -501,18 +600,19 @@ function renameFile(ctx, wopiParams, name) {
return res;
});
}
-function checkFileInfo(ctx, uri, access_token, sc) {
+function checkFileInfo(ctx, wopiSrc, access_token, opt_sc) {
return co(function* () {
let fileInfo = undefined;
try {
ctx.logger.info('wopi checkFileInfo start');
+ let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`;
let filterStatus = yield checkIpFilter(ctx, uri);
if (0 !== filterStatus) {
return fileInfo;
}
let headers = {};
- if (sc) {
- headers['X-WOPI-SessionContext'] = sc;
+ if (opt_sc) {
+ headers['X-WOPI-SessionContext'] = opt_sc;
}
fillStandardHeaders(headers, uri, access_token);
ctx.logger.debug('wopi checkFileInfo request uri=%s headers=%j', uri, headers);
@@ -650,12 +750,22 @@ function checkIpFilter(ctx, uri){
return filterStatus;
});
}
+function getWopiParams(lockId, fileInfo, wopiSrc, access_token, access_token_ttl) {
+ let commonInfo = {lockId: lockId, fileInfo: fileInfo};
+ let userAuth = {
+ wopiSrc: wopiSrc, access_token: access_token, access_token_ttl: access_token_ttl,
+ hostSessionId: null, userSessionId: null, mode: null
+ };
+ return {commonInfo: commonInfo, userAuth: userAuth, LastModifiedTime: null};
+};
exports.discovery = discovery;
exports.collaboraCapabilities = collaboraCapabilities;
exports.parseWopiCallback = parseWopiCallback;
exports.getEditorHtml = getEditorHtml;
+exports.getConverterHtml = getConverterHtml;
exports.putFile = putFile;
+exports.putRelativeFile = putRelativeFile;
exports.renameFile = renameFile;
exports.lock = lock;
exports.unlock = unlock;