Move stream timeout promise to util

Signed-off-by: Otto Sichert <git@ottosichert.de>
This commit is contained in:
Otto Sichert
2022-06-15 12:16:32 +02:00
committed by blam
parent cd973a35d8
commit 67e2671e23
2 changed files with 37 additions and 23 deletions
@@ -24,6 +24,7 @@ import {
ReadTreeResponseDirOptions,
ReadTreeResponseFile,
} from '../types';
import { streamToTimeoutPromise } from './util';
/**
* Wraps a zip archive stream into a tree response reader.
@@ -86,27 +87,9 @@ export class ZipArchiveResponse implements ReadTreeResponse {
const files = Array<ReadTreeResponseFile>();
// Some corrupted ZIP files cause the zlib inflater to hang indefinitely.
// This is a workaround to bail on stuck streams after 3 seconds.
const piped = this.stream.pipe(unzipper.Parse());
let lastEntryTimeout: NodeJS.Timeout | undefined;
await piped
const parseStream = this.stream
.pipe(unzipper.Parse())
.on('entry', (entry: Entry) => {
clearTimeout(lastEntryTimeout);
lastEntryTimeout = setTimeout(() => {
piped.emit(
'error',
new Error(`Timed out unzipping file ${entry.path}`),
);
}, 3000);
if (entry.type === 'Directory') {
entry.resume();
return;
}
if (this.shouldBeIncluded(entry)) {
files.push({
path: this.getInnerPath(entry.path),
@@ -115,10 +98,14 @@ export class ZipArchiveResponse implements ReadTreeResponse {
} else {
entry.autodrain();
}
})
.promise();
});
clearTimeout(lastEntryTimeout);
await streamToTimeoutPromise(parseStream, {
eventName: 'entry',
timeoutMs: 3000,
getError: (entry: Entry) =>
new Error(`Timed out while unzipping ${entry.type}: ${entry.path}`),
});
return files;
}
@@ -23,3 +23,30 @@ const directoryNameRegex = /^[^\/]+\//;
export function stripFirstDirectoryFromPath(path: string): string {
return path.replace(directoryNameRegex, '');
}
// Some corrupted ZIP files cause the zlib inflater to hang indefinitely.
// This is a workaround to bail on stuck streams after 3 seconds.
export async function streamToTimeoutPromise<T>(
stream: NodeJS.ReadableStream,
options: {
timeoutMs: number;
eventName: string;
getError: (data: T) => Error;
},
) {
let lastEntryTimeout: NodeJS.Timeout | undefined;
stream.on(options.eventName, (data: T) => {
clearTimeout(lastEntryTimeout);
lastEntryTimeout = setTimeout(() => {
stream.emit('error', options.getError(data));
}, options.timeoutMs);
});
await new Promise(function (resolve, reject) {
stream.on('finish', resolve);
stream.on('error', reject);
});
clearTimeout(lastEntryTimeout);
}