Leantime/leantime · error · Exception
notification.plugin_zip_invalid_arg
Error message
notification.plugin_zip_invalid_arg
What it means
The match maps ZipArchive::ER_INVAL to 'Zip: Invalid argument'. libzip returns ER_INVAL for an invalid filename/flags combination or when the archive contains invalid entries. Here the filename handed to open() is derived by naive substr() parsing of the Content-Disposition header (everything after 'filename='), so a missing, empty, or malformed header produces an empty or bogus path like '/tmp/' and open() fails with ER_INVAL.
Source
Thrown at app/Domain/Plugins/Services/Plugins.php:737
}
if (
is_dir($pluginDir = "{$this->pluginDirectory}{$foldername}")
&& ! File::deleteDirectory($pluginDir)
) {
throw new \Exception(__('notification.plugin_cant_remove'));
}
if (! mkdir($pluginDir) && ! is_dir($pluginDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $pluginDir));
}
$zip = new \ZipArchive;
match ($zip->open($temporaryFile)) {
\ZipArchive::ER_EXISTS => throw new \Exception(__('notification.plugin_zip_exists')),
\ZipArchive::ER_INCONS => throw new \Exception(__('notification.plugin_zip_inconsistent')),
\ZipArchive::ER_INVAL => throw new \Exception(__('notification.plugin_zip_invalid_arg')),
\ZipArchive::ER_MEMORY => throw new \Exception(__('notification.plugin_zip_malloc')),
\ZipArchive::ER_NOENT => throw new \Exception(__('notification.plugin_zip_no_file')),
\ZipArchive::ER_NOZIP => throw new \Exception(__('notification.plugin_zip_not_zip')),
\ZipArchive::ER_OPEN => throw new \Exception(__('notification.plugin_zip_cant_open')),
\ZipArchive::ER_READ => throw new \Exception(__('notification.plugin_zip_read_err')),
\ZipArchive::ER_SEEK => throw new \Exception(__('notification.plugin_zip_seek_err')),
default => throw new \Exception(__('notification.plugin_zip_unknown_err')),
true => null,
};
if (! $zip->extractTo($pluginDir)) {
throw new \Exception(__('notification.plugin_zip_cant_extract'));
}
$zip->close();
unlink($temporaryFile);
View on GitHub (pinned to 9a9f49f100)
Solutions
- curl -sD - -o /dev/null the download endpoint (with license headers) and inspect the exact Content-Disposition value.
- Patch the parsing to a regex that handles quotes and trailing parameters (see exampleFix) and fails loudly on absence.
- If the header is genuinely absent server-side, report it - the plugin cannot be installed under this Leantime version otherwise.
- Clear any stale /tmp file created from a previous malformed attempt.
Example fix
// before
$filename = $response->header('Content-Disposition');
$filename = substr($filename, strpos($filename, 'filename=') + 9);
// after: regex that handles quotes/parameters and fails loudly when absent
if (! preg_match('/filename="?([^";]+)\.zip"?/i', (string) $response->header('Content-Disposition'), $m)) {
throw new \Exception('Malformed Content-Disposition header from marketplace');
}
$filename = $m[1]; Defensive patterns
Strategy: validation
Validate before calling
if (! preg_match('/filename="?([^";]+\.zip)"?/i', (string) $response->header('Content-Disposition'), $m)) {
throw new RuntimeException('Marketplace response lacks a parsable filename - cannot install');
}
$foldername = Str::studly(basename($m[1], '.zip')); Prevention
- Validate Content-Disposition with a regex rather than strpos/substr before using it as a filename.
- Fail loudly when the header is missing instead of deriving an empty path.
- Watch marketplace API changes when upgrading Leantime versions.
When it happens
Trigger: installMarketplacePlugin() where the marketplace response's Content-Disposition header lacks 'filename=' (substr yields '' -> path '/tmp/'), contains quotes/semicolons the substr() logic does not strip, or carries path separators; alternatively the zip contains entries with invalid names.
Common situations: Marketplace API or CDN changing its Content-Disposition format; a proxy stripping the header; header value like 'attachment; filename="Name.zip"; size=123' leaving trailing quotes/params in the parsed name.
Related errors
- notification.plugin_zip_exists
- notification.plugin_zip_inconsistent
- notification.plugin_zip_malloc
- notification.plugin_zip_no_file
- notification.plugin_zip_not_zip
AI-assisted analysis of Leantime/leantime@9a9f49f100 (2026-08-21).
Data as JSON: /api/errors/755699259fe132ab.
Report an issue: GitHub.