[bug] Refactor pipeStreams to prevent infinite printFile calls

This commit is contained in:
Sergey Konovalov
2025-08-22 14:59:49 +03:00
parent 98c85fc07d
commit 87d655a25d
5 changed files with 28 additions and 31 deletions

View File

@ -36,6 +36,7 @@ const { cp, rm, mkdir } = require('fs/promises');
const { stat, readFile, writeFile } = require('fs/promises');
var path = require('path');
var utils = require("../utils");
const { pipeline } = require('node:stream/promises');
function getFilePath(storageCfg, strPath) {
@ -76,7 +77,7 @@ async function putObject(storageCfg, strPath, buffer, contentLength) {
await writeFile(fsPath, buffer);
} else {
let writable = await utils.promiseCreateWriteStream(fsPath);
await utils.pipeStreams(buffer, writable, true);
await pipeline(buffer, writable);
}
}

View File

@ -926,24 +926,22 @@ function changeOnlyOfficeUrl(inputUrl, strPath, optFilename) {
return inputUrl + constants.ONLY_OFFICE_URL_PARAM + '=' + constants.OUTPUT_NAME + path.extname(optFilename || strPath);
}
exports.changeOnlyOfficeUrl = changeOnlyOfficeUrl;
function pipeStreams(from, to, isEnd) {
return new Promise(function(resolve, reject) {
from.pipe(to, {end: isEnd});
from.on('end', function() {
resolve();
});
from.on('error', function(e) {
reject(e);
});
/**
* Pipe streams for HTTP responses, swallowing client abort errors.
* @param {NodeJS.ReadableStream} from - source stream
* @param {NodeJS.WritableStream} to - HTTP response stream
* @returns {Promise<void>}
*/
function pipeHttpStreams(from, to) {
return pipeline(from, to).catch((err) => {
// Treat client abort/connection reset as non-fatal to keep "End" logs parity.
if (err && (err.code === 'ERR_STREAM_PREMATURE_CLOSE' || err.code === 'ECONNRESET' || err.code === 'EPIPE')) {
return;
}
throw err;
});
}
exports.pipeStreams = pipeStreams;
function* pipeFiles(from, to) {
var fromStream = yield promiseCreateReadStream(from);
var toStream = yield promiseCreateWriteStream(to);
yield pipeStreams(fromStream, toStream, true);
}
exports.pipeFiles = co.wrap(pipeFiles);
exports.pipeHttpStreams = pipeHttpStreams;
function checkIpFilter(ctx, ipString, opt_hostname) {
const tenIpFilterRules = ctx.getCfg('services.CoAuthoring.ipfilter.rules', cfgIpFilterRules);

View File

@ -1638,7 +1638,7 @@ exports.printFile = function(req, res) {
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);

View File

@ -607,7 +607,7 @@ function convertTo(req, res) {
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);

View File

@ -41,6 +41,7 @@ var spawnAsync = require('@expo/spawn-async');
const bytes = require('bytes');
const lcid = require('lcid');
const ms = require('ms');
const { pipeline } = require('node:stream/promises');
var commonDefines = require('./../../Common/sources/commondefines');
var storage = require('./../../Common/sources/storage/storage-base');
@ -566,23 +567,20 @@ function* concatFiles(source, template) {
//concatenate EditorN.ext parts in Editor.ext
let list = yield utils.listObjects(source, true);
list.sort(utils.compareStringByLength);
let writeStreams = {};
const createdTargets = new Set();
for (let i = 0; i < list.length; ++i) {
let file = list[i];
if (file.match(new RegExp(`${template}\\d+\\.`))) {
let target = file.replace(new RegExp(`(${template})\\d+(\\..*)`), '$1$2');
let writeStream = writeStreams[target];
if (!writeStream) {
writeStream = yield utils.promiseCreateWriteStream(target);
writeStreams[target] = writeStream;
}
const isFirst = !createdTargets.has(target);
const writeOpts = isFirst ? undefined : { flags: 'a' };
let writeStream = yield utils.promiseCreateWriteStream(target, writeOpts);
let readStream = yield utils.promiseCreateReadStream(file);
yield utils.pipeStreams(readStream, writeStream, false);
}
}
for (let i in writeStreams) {
if (writeStreams.hasOwnProperty(i)) {
writeStreams[i].end();
// Use raw pipeline for file-to-file operations to surface real errors
yield pipeline(readStream, writeStream);
if (isFirst) {
createdTargets.add(target);
}
}
}
}