diff --git a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts index 50972a52a4..ffc2c8d9a7 100644 --- a/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts +++ b/packages/backend-common/src/reading/tree/ZipArchiveResponse.ts @@ -118,7 +118,7 @@ export class ZipArchiveResponse implements ReadTreeResponse { } const archive = archiver('zip'); - await this.stream + const parseStream = this.stream .pipe(unzipper.Parse()) .on('entry', (entry: Entry) => { if (entry.type === 'File' && this.shouldBeIncluded(entry)) { @@ -126,8 +126,15 @@ export class ZipArchiveResponse implements ReadTreeResponse { } else { entry.autodrain(); } - }) - .promise(); + }); + + await streamToTimeoutPromise(parseStream, { + eventName: 'entry', + timeoutMs: 3000, + getError: (entry: Entry) => + new Error(`Timed out while unzipping ${entry.type}: ${entry.path}`), + }); + archive.finalize(); return archive; @@ -140,7 +147,7 @@ export class ZipArchiveResponse implements ReadTreeResponse { options?.targetDir ?? (await fs.mkdtemp(platformPath.join(this.workDir, 'backstage-'))); - await this.stream + const parseStream = this.stream .pipe(unzipper.Parse()) .on('entry', async (entry: Entry) => { // Ignore directory entries since we handle that with the file entries @@ -155,8 +162,14 @@ export class ZipArchiveResponse implements ReadTreeResponse { } else { entry.autodrain(); } - }) - .promise(); + }); + + await streamToTimeoutPromise(parseStream, { + eventName: 'entry', + timeoutMs: 3000, + getError: (entry: Entry) => + new Error(`Timed out while unzipping ${entry.type}: ${entry.path}`), + }); return dir; } diff --git a/packages/backend-common/src/reading/tree/util.ts b/packages/backend-common/src/reading/tree/util.ts index 51f904f313..1d7a42c93e 100644 --- a/packages/backend-common/src/reading/tree/util.ts +++ b/packages/backend-common/src/reading/tree/util.ts @@ -26,6 +26,7 @@ export function stripFirstDirectoryFromPath(path: string): string { // Some corrupted ZIP files cause the zlib inflater to hang indefinitely. // This is a workaround to bail on stuck streams after 3 seconds. +// Related: https://github.com/ZJONSSON/node-unzipper/issues/213 export async function streamToTimeoutPromise( stream: NodeJS.ReadableStream, options: {