Merge pull request 'fix/pre9.0.4' (#45) from fix/pre9.0.4 into hotfix/v9.0.4

Reviewed-on: https://git.onlyoffice.com/ONLYOFFICE/server/pulls/45
This commit is contained in:
Sergey Konovalov
2025-07-08 17:46:54 +00:00
4 changed files with 64 additions and 13 deletions

View File

@ -1031,7 +1031,8 @@ const c_oPublishType = {
closeConnection: 13,
changesNotify: 14,
changeConnecitonInfo: 15,
rpc: 16
rpc: 16,
updateVersion: 17
};
const c_oAscCsvDelimiter = {
None: 0,

View File

@ -290,7 +290,12 @@ exports.UPDATE_VERSION = 'update version';
exports.NO_CACHE_CODE = 4009;
exports.NO_CACHE = 'no cache';
exports.RESTORE_CODE = 4010;
exports.RESTORE = 'no cache';
exports.RESTORE = 'restore';
exports.QUIET_CODE = 4011;//browser only
exports.QUIET = 'quiet';
exports.RECONNECT_FAILED_CODE = 4012;//browser only
exports.RECONNECT_FAILED = 'reconnect failed';
//update connection error codes
exports.CONTENT_DISPOSITION_INLINE = 'inline';
exports.CONTENT_DISPOSITION_ATTACHMENT = 'attachment';

View File

@ -3800,6 +3800,13 @@ exports.install = function(server, callbackFunction) {
sendDataRpc(ctx, participant, data.responseKey, data.data);
});
break;
case commonDefines.c_oPublishType.updateVersion:
// To finalize form or refresh file in live view
participants = getParticipants(data.docId);
_.each(participants, function(participant) {
sendData(ctx, participant, {type: "updateVersion", success: data.success});
});
break;
default:
ctx.logger.debug('pubsub unknown message type:%s', msg);
}

View File

@ -972,6 +972,10 @@ const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) {
const userLastChangeId = cmd.getUserId() || cmd.getUserActionId();
const userLastChangeIndex = cmd.getUserIndex() || cmd.getUserActionIndex();
let replyStr;
let isSfcmSuccess = false;
let isSfcSuccess = false;
let needRetry = false;
let needUpdateVersionEvent = !isSfcm && !isEncrypted;
if (constants.EDITOR_CHANGES !== statusInfo || isSfcm) {
var saveKey = docId + cmd.getSaveKey();
var isError = constants.NO_ERROR != statusInfo;
@ -997,9 +1001,7 @@ const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) {
}
lastOpenDate = row.last_open_date;
}
var isSfcmSuccess = false;
let storeForgotten = false;
let needRetry = false;
var statusOk;
var statusErr;
if (isSfcm) {
@ -1198,6 +1200,7 @@ const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) {
ctx.logger.warn('sendServerRequest returned an error: data = %s', replyStr);
}
if (requestRes) {
isSfcSuccess = true;
updateIfTask = undefined;
yield docsCoServer.cleanDocumentOnExitPromise(ctx, docId, true, callbackUserIndex);
if (isOpenFromForgotten) {
@ -1217,7 +1220,10 @@ const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) {
}
} else {
updateIfTask = undefined;
needUpdateVersionEvent = false;
}
} else {
needUpdateVersionEvent = false;
}
}
} else {
@ -1253,7 +1259,7 @@ const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) {
//cleanupRes can be false in case of simultaneous opening. it is OK
let cleanupRes = yield cleanupCacheIf(ctx, updateMask);
ctx.logger.debug('storeForgotten cleanupRes=%s', cleanupRes);
}
}
}
if (forceSave) {
yield* docsCoServer.setForceSave(ctx, docId, forceSave, cmd, isSfcmSuccess && !isError, outputSfc?.getUrl());
@ -1273,6 +1279,10 @@ const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) {
yield docsCoServer.cleanDocumentOnExitNoChangesPromise(ctx, docId, undefined, userLastChangeIndex, true);
}
if (needUpdateVersionEvent && !needRetry) {
yield docsCoServer.publish(ctx, {type: commonDefines.c_oPublishType.updateVersion, ctx: ctx, docId: docId, success: isSfcSuccess});
}
if ((docsCoServer.getIsShutdown() && !isSfcm) || cmd.getRedisKey()) {
let keyRedis = cmd.getRedisKey() ? cmd.getRedisKey() : redisKeyShutdown;
yield docsCoServer.editorStat.removeShutdown(keyRedis, docId);
@ -1650,17 +1660,32 @@ exports.printFile = function(req, res) {
}
});
};
/**
* Proxy download file request to the file storage
* @param {object} req - The HTTP request object
* @param {object} res - The HTTP response object
* @returns {Promise}
*/
exports.downloadFile = function(req, res) {
return co(function*() {
let ctx = new operationContext.Context();
let stream = null;
try {
let startDate = null;
if (clientStatsD) {
startDate = new Date();
}
const docId = req.params.docid;
if (!docId) {
res.status(400).send('docid is required');
return;
}
ctx.initFromRequest(req);
yield ctx.initTenantCache();
ctx.setDocId(req.params.docid);
ctx.setDocId(docId);
//todo remove in 8.1. For compatibility
let url = req.get('x-url');
if (url) {
@ -1679,7 +1704,7 @@ exports.downloadFile = function(req, res) {
let headers, fromTemplate;
let authRes = yield docsCoServer.getRequestParams(ctx, req);
if (authRes.code === constants.NO_ERROR) {
let decoded = authRes.params;
const decoded = authRes.params;
if (decoded.changesUrl) {
url = decoded.changesUrl;
isInJwtToken = true;
@ -1723,12 +1748,12 @@ exports.downloadFile = function(req, res) {
}
if (fromTemplate) {
ctx.logger.debug('downloadFile from file template: %s', fromTemplate);
let locale = constants.TEMPLATES_DEFAULT_LOCALE;
let fileTemplatePath = pathModule.join(tenNewFileTemplate, locale, 'new.' + fromTemplate);
const locale = constants.TEMPLATES_DEFAULT_LOCALE;
const fileTemplatePath = pathModule.join(tenNewFileTemplate, locale, 'new.' + fromTemplate);
res.sendFile(pathModule.resolve(fileTemplatePath));
} else {
if (utils.canIncludeOutboxAuthorization(ctx, url)) {
let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox);
const secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox);
authorization = utils.fillJwtForRequest(ctx, {url: url}, secret, false);
}
let urlParsed = urlModule.parse(url);
@ -1746,7 +1771,9 @@ exports.downloadFile = function(req, res) {
headers['Range'] = req.get('Range');
}
const { response, stream } = yield utils.downloadUrlPromise(ctx, url, tenDownloadTimeout, tenDownloadMaxBytes, authorization, isInJwtToken, headers, true);
const downloadResult = yield utils.downloadUrlPromise(ctx, url, tenDownloadTimeout, tenDownloadMaxBytes, authorization, isInJwtToken, headers, true);
const response = downloadResult.response;
stream = downloadResult.stream;
//Set-Cookie resets browser session
delete response.headers['set-cookie'];
// Set the response headers to match the target response
@ -1763,6 +1790,9 @@ exports.downloadFile = function(req, res) {
catch (err) {
if (err.code === "ERR_STREAM_PREMATURE_CLOSE") {
ctx.logger.debug('Error downloadFile: %s', err.stack);
if (!res.headersSent) {
res.sendStatus(499);
}
} else {
ctx.logger.error('Error downloadFile: %s', err.stack);
//catch errors because status may be sent while piping to response
@ -1772,8 +1802,8 @@ exports.downloadFile = function(req, res) {
res.sendStatus(408);
} else if (err.code === 'EMSGSIZE') {
res.sendStatus(413);
} else if (err.response) {
res.sendStatus(err.response.statusCode);
} else if (err.statusCode) {
res.sendStatus(err.statusCode);
} else {
res.sendStatus(400);
}
@ -1784,6 +1814,14 @@ exports.downloadFile = function(req, res) {
}
}
finally {
// Ensure stream is properly destroyed
if (stream && typeof stream.destroy === 'function') {
try {
stream.destroy();
} catch (destroyErr) {
ctx.logger.warn('Error destroying stream: %s', destroyErr.stack);
}
}
ctx.logger.info('End downloadFile');
}
});