unzipper
semver
>=0.9.0 <0.13.0postconditions17functions9last verified2026-04-17coverage score75%Postconditions — what we check
- Parse · corrupt-zip-errorerrorWhenInput stream contains a corrupt or invalid zip fileThrows
Error emitted on the Parse stream (e.g., 'end of file' or Zlib Z_BUF_ERROR)Required handlingCaller MUST attach an error handler to the Parse stream. Use parseStream.on('error', handler) or handle in the pipe chain. Without an error handler, corrupt zip errors will crash the process as unhandled stream errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Parse · entry-not-consumederrorWhenAn entry stream emitted by Parse is not read or drainedThrows
The entire parse stream backs up and hangs indefinitelyRequired handlingCaller MUST either consume each entry's stream data or call entry.autodrain() on entries that are not needed. Failure to drain entries causes the stream to stall.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Extract · extraction-io-errorerrorWhenFile system error during extraction (permission denied, disk full, invalid path)Throws
Error emitted on the Extract streamRequired handlingCaller MUST attach an error handler or use promise-based .promise() method. Example: fs.createReadStream(zipFile).pipe(unzipper.Extract({path})).promise() The promise form rejects with the IO error allowing try-catch handling.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - Extract · corrupt-zip-extract-errorerrorWhenInput contains a corrupt zip file during ExtractThrows
Error emitted on the Extract streamRequired handlingCaller MUST handle stream errors when using Extract. Use .promise() for await-able error handling or attach an error listener to the stream. Files partially extracted before the error are NOT automatically cleaned up.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Open · file-not-founderrorWhenThe file path passed to Open.file() does not exist or cannot be readThrows
Error with ENOENT or EACCES codeRequired handlingCaller MUST wrap Open.file() in try-catch or handle promise rejection. Open.file() returns a promise that rejects with filesystem errors.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - Open · invalid-zip-open-errorerrorWhenFile exists but is not a valid zip file (wrong magic bytes or corrupt central directory)Throws
Error indicating invalid zip formatRequired handlingCaller MUST wrap Open.file() in try-catch. Invalid zip files cause rejection when the central directory cannot be parsed. Validate the file is a zip before processing.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - Open · entry-extraction-errorerrorWhenA specific entry within the zip cannot be extracted (corrupt entry, unsupported compression)Throws
Error emitted on the entry extraction streamRequired handlingCaller MUST handle errors on individual entry streams. attach .on('error', handler) to each entry's stream, or use entry.buffer() in a try-catch block. Entry errors do not propagate to the parent directory stream automatically.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - ParseOne · parseone-pattern-not-founderrorWhenfs.createReadStream(zipFile).pipe(unzipper.ParseOne(/pattern/)) is piped and the source archive contains no entries matching the provided regex or string pattern. The Duplex stream fully processes all entries and emits 'PATTERN_NOT_FOUND' error on the output stream after the archive completes. Also triggered when the archive is empty. This is the most common silent failure in code using ParseOne — callers that don't attach an error handler receive an unhandled stream error that crashes the process.Throws
Error with message 'PATTERN_NOT_FOUND' — emitted as a stream error event on the output (Duplex) stream returned by ParseOne(). This is NOT a thrown synchronous exception — it propagates via stream.emit('error', ...) or via the promise rejection when using .buffer(). Confirmed from parseOne.js: `outStream.emit('error', new Error('PATTERN_NOT_FOUND'))` when the 'finish' event fires without setting `found = true`.Required handlingUse the .buffer() promise form with try-catch for reliable error handling: try { const content = await fs.createReadStream(zipFile) .pipe(unzipper.ParseOne(/config\.json$/)) .buffer(); return JSON.parse(content.toString()); } catch (error) { if (error.message === 'PATTERN_NOT_FOUND') { console.warn('Expected file not found in archive'); return null; // Or throw a more descriptive error } throw error; } When using the stream form (without .buffer()), attach an error handler: const extracted = stream.pipe(unzipper.ParseOne('target.txt')); extracted.on('error', (err) => { if (err.message === 'PATTERN_NOT_FOUND') { ... } }); Note: ParseOne() without a pattern extracts the FIRST entry — no PATTERN_NOT_FOUND is possible unless the archive itself is empty or corrupt.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - ParseOne · parseone-corrupt-archive-errorerrorWhenThe source ZIP stream contains a corrupt or invalid archive (invalid signature, truncated data, malformed local file headers). The Parse stream underlying ParseOne emits an error that propagates to the ParseOne output stream. This is the same corruption error as the Parse contract, but specific to the single-entry extraction workflow.Throws
Error with message 'invalid signature: 0x<hex>' — from parse.js when local file header signature bytes do not match 0x04034b50. Error with message 'FILE_ENDED' — from PullStream.js when the input stream ends before expected bytes are read (truncated archive). Both propagate via stream.emit('error', ...) or reject .buffer() promise.Required handlingAlways handle errors when piping into ParseOne: try { const buffer = await readStream .pipe(unzipper.ParseOne('README.md')) .buffer(); return buffer.toString(); } catch (error) { if (error.message && error.message.startsWith('invalid signature')) { throw new Error('Not a valid ZIP file'); } if (error.message === 'FILE_ENDED') { throw new Error('ZIP file is truncated or incomplete'); } throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Open.buffer · open-buffer-invalid-ziperrorWhenThe Buffer passed to Open.buffer() does not contain a valid ZIP archive (wrong magic bytes, no central directory, corrupt structure). The directory parser reads from the buffer's tail looking for the end-of-central-directory record, and if the buffer is not a ZIP (e.g., a PDF, an image, or empty bytes), the directory parser will emit FILE_ENDED or invalid signature errors.Throws
Error with message 'FILE_ENDED' — when PullStream exhausts the buffer before finding the end-of-central-directory record. Common when the buffer is not a ZIP file at all, or is empty/truncated. Error with message 'invalid zip64 end of central dir locator signature ...' — when the buffer contains what looks like a ZIP64 header but has corrupt signature bytes. These propagate as Promise rejections from Open.buffer().Required handlingWrap Open.buffer() in try-catch: try { const directory = await unzipper.Open.buffer(fileBuffer); const files = await directory.files; for (const file of files) { if (file.type === 'File') { const content = await file.buffer(); // process content } } } catch (error) { if (error.message === 'FILE_ENDED') { throw new Error('Buffer is not a valid ZIP archive (possibly empty or wrong format)'); } throw error; } For user-uploaded files, validate the content-type header and the magic bytes (ZIP files start with 0x504B0304 — 'PK\x03\x04') before calling Open.buffer().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Open.url · open-url-missing-content-lengthwarningWhenThe remote server does not return a Content-Length header in its response. Open.url() makes a HEAD-like request to determine the file size before making range requests. If the server omits Content-Length (many CDNs with chunked transfer encoding, servers with gzip compression enabled, or servers that don't support it), the size() call rejects immediately with 'Missing content length header'. This is a common failure when using Open.url() against APIs or cloud storage endpoints that do not expose file size in headers.Throws
Error with message 'Missing content length header' — confirmed from Open/index.js: `reject(new Error('Missing content length header'))` when the response to the initial size-detection request has no Content-Length. This is a Promise rejection (not a stream error) since the failure occurs during the initial setup of the CentralDirectory, before any streaming begins.Required handlingWrap Open.url() in try-catch and handle the 'Missing content length header' error specifically: const request = require('request'); // Or an alternative HTTP library try { const directory = await unzipper.Open.url(request, { url: 'https://example.com/archive.zip', headers: { 'User-Agent': 'MyApp/1.0' } }); const files = await directory.files; // process files } catch (error) { if (error.message === 'Missing content length header') { // Fall back to downloading the full file console.warn('Remote server does not support range requests, falling back to full download'); const response = await fetch(zipUrl); const buffer = Buffer.from(await response.arrayBuffer()); const directory = await unzipper.Open.buffer(buffer); // ... } throw error; } Note: Open.url() is typically used with the deprecated `request` library. For modern code, prefer downloading the full ZIP with fetch() and using Open.buffer() instead.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Open.url · open-url-network-errorerrorWhenOpen.url() fails because the URL is unreachable (DNS failure, connection refused, SSL error, HTTP error response). The underlying request library emits an 'error' event that is forwarded to the Promise rejection.Throws
Error from the request library — the specific type depends on which library is passed as the first argument. With the `request` library, network errors are Error objects with message like 'ENOTFOUND', 'ECONNREFUSED', or 'ETIMEDOUT'. HTTP 4xx/5xx responses do NOT automatically reject — the request library's behavior determines this.Required handlingWrap Open.url() in try-catch and handle network errors: try { const directory = await unzipper.Open.url(request, zipUrl); } catch (error) { console.error('Failed to open remote ZIP:', error.message); throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - Open.s3_v3 · open-s3v3-not-found-or-access-deniederrorWhenOpen.s3_v3() is called and either: (a) The S3 object does not exist (NoSuchKey from HeadObjectCommand) (b) The bucket does not exist (NoSuchBucket) (c) The caller does not have GetObject/HeadObject permissions (AccessDenied / 403) The HeadObjectCommand is sent during the initial size() call inside directory(), and its rejection propagates as a Promise rejection from Open.s3_v3().Throws
AWS SDK v3 S3ServiceException subclasses: - NoSuchKey (404) — S3 object doesn't exist at the specified key - NoSuchBucket (404) — S3 bucket doesn't exist - S3ServiceException with $metadata.httpStatusCode === 403 — access denied These are thrown by client.send() and propagate through the async HeadObjectCommand. Check: error.$metadata?.httpStatusCode or error.name for specific error types.Required handlingWrap Open.s3_v3() in try-catch and handle common S3 errors: const { S3Client, NoSuchKey } = require('@aws-sdk/client-s3'); const s3 = new S3Client({ region: 'us-east-1' }); try { const directory = await unzipper.Open.s3_v3(s3, { Bucket: 'my-bucket', Key: 'archives/data.zip', }); const files = await directory.files; for (const file of files) { if (file.type === 'File') { const content = await file.buffer(); // process content } } } catch (error) { if (error.name === 'NoSuchKey') { throw new Error(`Archive not found: ${params.Key}`); } if (error.$metadata?.httpStatusCode === 403) { throw new Error('Access denied to S3 archive — check IAM permissions'); } throw error; }costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - CentralDirectory.extract · extract-path-missingerrorWhenCentralDirectory.extract() is called without an opts.path parameter, or with opts.path set to undefined/null/empty string. The path is required for extracting files to disk. The function throws synchronously (not as a Promise rejection) when this check fails, which means the error may propagate differently depending on whether the caller is in an async context.Throws
Error with message 'PATH_MISSING' — thrown synchronously in the extract function: `if (!opts || !opts.path) throw new Error('PATH_MISSING')`. Confirmed from directory.js. In an async function awaiting extract(), this synchronous throw IS caught as a Promise rejection by the async function wrapper. In .then() chains, it also propagates as a rejection. Always use try-catch.Required handlingAlways provide opts.path and wrap in try-catch: const directory = await unzipper.Open.file('archive.zip'); try { await directory.extract({ path: '/tmp/extracted', concurrency: 4 }); } catch (error) { if (error.message === 'PATH_MISSING') { throw new Error('Extraction path is required'); } throw error; } Use path.resolve() to get an absolute path — the function normalizes the path internally with path.resolve(path.normalize(opts.path)).costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - CentralDirectory.extract · extract-filesystem-errorerrorWhenCentralDirectory.extract() fails because of filesystem errors during extraction: - Destination path does not exist and cannot be created (EACCES, EROFS) - Disk is full (ENOSPC) - Destination path is read-only (EACCES) - An individual entry file stream encounters a compression error The extract() method uses fs-extra's ensureDir() and fs.createWriteStream() for each entry. Failures in any entry propagate as Promise rejections via Bluebird.map's error handling.Throws
Error with EACCES — permission denied writing to destination directory. Error with ENOSPC — no space left on device. Error with ENOENT — parent directory of a nested path cannot be created (rare, since ensureDir is used, but can happen with race conditions). Entry stream errors (zlib errors like 'Z_BUF_ERROR') for corrupt compressed entries.Required handlingWrap extract() in try-catch and handle common filesystem errors: const { mkdir } = require('fs/promises'); const destPath = path.resolve('/tmp', 'extracted-' + Date.now()); await mkdir(destPath, { recursive: true }); const directory = await unzipper.Open.file('archive.zip'); try { await directory.extract({ path: destPath }); } catch (error) { if (error.code === 'EACCES') { throw new Error(`Cannot write to ${destPath}: permission denied`); } if (error.code === 'ENOSPC') { throw new Error('Disk full — extraction aborted'); } throw error; }costhighin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[8] - Entry.buffer · entry-buffer-missing-passworderrorWhenfile.buffer() (or entry.buffer()) is called on an encrypted ZIP entry without providing the password, or with a wrong password. Encrypted ZIP entries have the encryption flag set (vars.flags & 0x01). The password check occurs during stream setup — before any decompressed data is read — so failure is immediate. This is common when processing user-uploaded ZIP files that may be encrypted, or when building ZIP processors that need to handle both encrypted and unencrypted archives.Throws
Error with message 'MISSING_PASSWORD' — thrown when the entry has encryption flag set but no password was provided. Confirmed from Open/unzip.js: `if (!_password) throw new Error('MISSING_PASSWORD')`. Error with message 'BAD_PASSWORD' — thrown when the provided password fails the verification byte check. Confirmed from Open/unzip.js: `if (header[11] !== check) throw new Error('BAD_PASSWORD')`. Both errors are thrown synchronously in the .vars Promise chain and propagate as rejections when awaiting file.buffer() or entry.buffer().Required handlingCheck for encrypted entries and handle appropriately: // When using Open.*: const directory = await unzipper.Open.file('archive.zip'); const files = await directory.files; for (const file of files) { if (file.type !== 'File') continue; try { const content = await file.buffer(password); // pass password or undefined processContent(content); } catch (error) { if (error.message === 'MISSING_PASSWORD') { console.error(`${file.path} is encrypted — password required`); continue; // Skip or prompt user } if (error.message === 'BAD_PASSWORD') { console.error(`Wrong password for ${file.path}`); continue; } throw error; } } // When using Parse() streams: stream.pipe(unzipper.Parse()).on('entry', async (entry) => { try { const content = await entry.buffer(); } catch (error) { if (error.message === 'MISSING_PASSWORD') { entry.autodrain(); // Drain the entry to keep the stream moving } } }); Note: The encryption flag can be checked via entry.vars (a Promise) — but this is internal API. Prefer try-catch with specific error messages.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible - Entry.buffer · entry-buffer-decompression-errorerrorWhenentry.buffer() or file.buffer() is called and the compressed entry data is corrupt or uses an unsupported compression method. The zlib.createInflateRaw() stream decompresses the data; if the compressed bytes are corrupt, zlib emits an error that propagates through the entry stream into BufferStream's rejection. Common with partially-downloaded ZIP files, network-interrupted transfers, or archives where individual entries are corrupt even though the central directory is intact.Throws
Error from zlib — typically 'invalid block type', 'unknown compression method', or 'Z_BUF_ERROR'. Propagates via entry.on('error', reject) in BufferStream. Error with message 'FILE_ENDED' — if the source stream ends before the expected compressed bytes are read (truncated entry).Required handlingWrap entry.buffer() / file.buffer() in try-catch for each entry independently: const directory = await unzipper.Open.buffer(zipBuffer); const files = await directory.files; const results = await Promise.allSettled( files .filter(f => f.type === 'File') .map(async (file) => { try { return { path: file.path, content: await file.buffer() }; } catch (error) { console.warn(`Failed to read ${file.path}: ${error.message}`); return { path: file.path, content: null, error: error.message }; } }) ); Use Promise.allSettled() rather than Promise.all() when processing multiple entries so one corrupt entry doesn't abort the entire batch.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisible
Sources
Every postcondition cites at least one of these. Numbered to match the footnotes above.
- [1]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper#readme
- [2]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/issues/213
- [3]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/issues/98
- [4]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/issues/286
- [5]raw.githubusercontent.com/ZJONSSON/node-unzipperhttps://raw.githubusercontent.com/ZJONSSON/node-unzipper/master/README.md
- [6]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/blob/master/lib/parseOne.js
- [7]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/blob/master/lib/parse.js
- [8]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/blob/master/lib/Open/directory.js
- [9]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/blob/master/lib/Open/index.js
- [10]docs.aws.amazon.com/AWSJavaScriptSDK/v3https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/s3/command/HeadObjectCommand/
- [11]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/blob/master/lib/Open/unzip.js
- [12]github.com/ZJONSSON/node-unzipperhttps://github.com/ZJONSSON/node-unzipper/blob/master/lib/BufferStream.js
Need a different package?
Request a profile