From 67a1033ded7c1a802cdc17bac6e00ab4a4fbcc7d Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Thu, 16 Nov 2023 19:25:21 -0500 Subject: [PATCH] fix: Allow for zip files with '/' path in central directory --- server/utils/ZipHelper.ts | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/server/utils/ZipHelper.ts b/server/utils/ZipHelper.ts index 3b212235e..1fdad2011 100644 --- a/server/utils/ZipHelper.ts +++ b/server/utils/ZipHelper.ts @@ -2,7 +2,7 @@ import path from "path"; import fs from "fs-extra"; import JSZip from "jszip"; import tmp from "tmp"; -import yauzl from "yauzl"; +import yauzl, { Entry, validateFileName } from "yauzl"; import { bytesToHumanReadable } from "@shared/utils/files"; import Logger from "@server/logging/Logger"; import { trace } from "@server/logging/tracing"; @@ -103,19 +103,32 @@ export default class ZipHelper { yauzl.open( filePath, - { lazyEntries: true, autoClose: true }, + { + lazyEntries: true, + autoClose: true, + // Filenames are validated inside on("entry") handler instead of within yauzl as some + // otherwise valid zip files (including those in our test suite) include / path. We can + // safely read but skip writing these. + // see: https://github.com/thejoshwolfe/yauzl/issues/135 + decodeStrings: false, + }, function (err, zipfile) { if (err) { return reject(err); } try { zipfile.readEntry(); - zipfile.on("entry", function (entry) { - Logger.debug("utils", "Extracting zip entry", entry); - if (/\/$/.test(entry.fileName)) { + zipfile.on("entry", function (entry: Entry) { + const fileName = Buffer.from(entry.fileName).toString("utf8"); + Logger.debug("utils", "Extracting zip entry", { fileName }); + + if (validateFileName(fileName)) { + Logger.warn("Invalid zip entry", { fileName }); + zipfile.readEntry(); + } else if (/\/$/.test(fileName)) { // directory file names end with '/' fs.mkdirp( - path.join(outputDir, entry.fileName), + path.join(outputDir, fileName), function (err: Error) { if (err) { throw err; @@ -131,15 +144,13 @@ export default class ZipHelper { } // ensure parent directory exists fs.mkdirp( - path.join(outputDir, path.dirname(entry.fileName)), + path.join(outputDir, path.dirname(fileName)), function (err) { if (err) { throw err; } readStream.pipe( - fs.createWriteStream( - path.join(outputDir, entry.fileName) - ) + fs.createWriteStream(path.join(outputDir, fileName)) ); readStream.on("end", function () { zipfile.readEntry();