diff --git a/public/lib/thirdpartylibs.xml b/public/lib/thirdpartylibs.xml
index 5da9e86072f..121eade2c00 100644
--- a/public/lib/thirdpartylibs.xml
+++ b/public/lib/thirdpartylibs.xml
@@ -611,7 +611,7 @@ All rights reserved.
zipstream
ZipStream-PHP
PHP ZIP Streaming Library
- 3.1.2
+ 3.2.0
MIT
https://github.com/maennchen/ZipStream-PHP
diff --git a/public/lib/zipstream/composer.json b/public/lib/zipstream/composer.json
index 6ecd503a66c..529c2445ddf 100644
--- a/public/lib/zipstream/composer.json
+++ b/public/lib/zipstream/composer.json
@@ -22,12 +22,12 @@
}
],
"require": {
- "php-64bit": "^8.2",
+ "php-64bit": "^8.3",
"ext-mbstring": "*",
"ext-zlib": "*"
},
"require-dev": {
- "phpunit/phpunit": "^11.0",
+ "phpunit/phpunit": "^12.0",
"guzzlehttp/guzzle": "^7.5",
"ext-zip": "*",
"mikey179/vfsstream": "^1.6",
@@ -57,7 +57,7 @@
"test:formatted": "@format --dry-run --stop-on-violation --using-cache=no",
"test:lint": "psalm --stats --show-info=true --find-unused-psalm-suppress",
"coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json --insecure",
- "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656 --trust-gpg-keys 0x8AC0BAA79732DD42",
+ "install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656 --trust-gpg-keys 0x8AC0BAA79732DD42 --trust-gpg-keys 0x6DA3ACC4991FFAE5",
"docs:generate": "tools/phpdocumentor --sourcecode"
},
"autoload": {
diff --git a/public/lib/zipstream/src/CompressionMethod.php b/public/lib/zipstream/src/CompressionMethod.php
index 51e43637025..2e1289cc366 100644
--- a/public/lib/zipstream/src/CompressionMethod.php
+++ b/public/lib/zipstream/src/CompressionMethod.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace ZipStream;
+/**
+ * @api
+ */
enum CompressionMethod: int
{
/**
diff --git a/public/lib/zipstream/src/Exception.php b/public/lib/zipstream/src/Exception.php
index 2e81e307bb3..b3f42e5f268 100644
--- a/public/lib/zipstream/src/Exception.php
+++ b/public/lib/zipstream/src/Exception.php
@@ -4,4 +4,7 @@ declare(strict_types=1);
namespace ZipStream;
+/**
+ * @api
+ */
abstract class Exception extends \Exception {}
diff --git a/public/lib/zipstream/src/Exception/DosTimeOverflowException.php b/public/lib/zipstream/src/Exception/DosTimeOverflowException.php
index b8d0508085d..360b5f8342c 100644
--- a/public/lib/zipstream/src/Exception/DosTimeOverflowException.php
+++ b/public/lib/zipstream/src/Exception/DosTimeOverflowException.php
@@ -8,7 +8,9 @@ use DateTimeInterface;
use ZipStream\Exception;
/**
- * This Exception gets invoked if a file wasn't found
+ * This Exception gets invoked if a DOS time is overflowing
+ *
+ * @api
*/
class DosTimeOverflowException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/FileNotFoundException.php b/public/lib/zipstream/src/Exception/FileNotFoundException.php
index 350a7bfe56f..b9b1a2b18ec 100644
--- a/public/lib/zipstream/src/Exception/FileNotFoundException.php
+++ b/public/lib/zipstream/src/Exception/FileNotFoundException.php
@@ -8,6 +8,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a file wasn't found
+ *
+ * @api
*/
class FileNotFoundException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/FileNotReadableException.php b/public/lib/zipstream/src/Exception/FileNotReadableException.php
index 93d0c6c64ea..2950423727e 100644
--- a/public/lib/zipstream/src/Exception/FileNotReadableException.php
+++ b/public/lib/zipstream/src/Exception/FileNotReadableException.php
@@ -7,7 +7,9 @@ namespace ZipStream\Exception;
use ZipStream\Exception;
/**
- * This Exception gets invoked if a file wasn't found
+ * This Exception gets invoked if a file isn't readable
+ *
+ * @api
*/
class FileNotReadableException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/FileSizeIncorrectException.php b/public/lib/zipstream/src/Exception/FileSizeIncorrectException.php
index 11f0b67b34d..e700669709f 100644
--- a/public/lib/zipstream/src/Exception/FileSizeIncorrectException.php
+++ b/public/lib/zipstream/src/Exception/FileSizeIncorrectException.php
@@ -8,6 +8,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a file is not as large as it was specified.
+ *
+ * @api
*/
class FileSizeIncorrectException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/OverflowException.php b/public/lib/zipstream/src/Exception/OverflowException.php
index 09bdafb2895..d96c4417533 100644
--- a/public/lib/zipstream/src/Exception/OverflowException.php
+++ b/public/lib/zipstream/src/Exception/OverflowException.php
@@ -8,6 +8,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a counter value exceeds storage size
+ *
+ * @api
*/
class OverflowException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/ResourceActionException.php b/public/lib/zipstream/src/Exception/ResourceActionException.php
index cbd9b0bb95e..81a2ea59868 100644
--- a/public/lib/zipstream/src/Exception/ResourceActionException.php
+++ b/public/lib/zipstream/src/Exception/ResourceActionException.php
@@ -8,6 +8,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a resource like `fread` returns false
+ *
+ * @api
*/
class ResourceActionException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/SimulationFileUnknownException.php b/public/lib/zipstream/src/Exception/SimulationFileUnknownException.php
index 717c1aafef3..240f825123e 100644
--- a/public/lib/zipstream/src/Exception/SimulationFileUnknownException.php
+++ b/public/lib/zipstream/src/Exception/SimulationFileUnknownException.php
@@ -9,6 +9,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a strict simulation is executed and the file
* information can't be determined without reading the entire file.
+ *
+ * @api
*/
class SimulationFileUnknownException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/StreamNotReadableException.php b/public/lib/zipstream/src/Exception/StreamNotReadableException.php
index c1446735a6c..b058f55d352 100644
--- a/public/lib/zipstream/src/Exception/StreamNotReadableException.php
+++ b/public/lib/zipstream/src/Exception/StreamNotReadableException.php
@@ -8,6 +8,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a stream can't be read.
+ *
+ * @api
*/
class StreamNotReadableException extends Exception
{
diff --git a/public/lib/zipstream/src/Exception/StreamNotSeekableException.php b/public/lib/zipstream/src/Exception/StreamNotSeekableException.php
index 606f11f1443..ecfd6e3f4cd 100644
--- a/public/lib/zipstream/src/Exception/StreamNotSeekableException.php
+++ b/public/lib/zipstream/src/Exception/StreamNotSeekableException.php
@@ -9,6 +9,8 @@ use ZipStream\Exception;
/**
* This Exception gets invoked if a non seekable stream is
* provided and zero headers are disabled.
+ *
+ * @api
*/
class StreamNotSeekableException extends Exception
{
diff --git a/public/lib/zipstream/src/File.php b/public/lib/zipstream/src/File.php
index 1498e4d6be2..148fc55b232 100644
--- a/public/lib/zipstream/src/File.php
+++ b/public/lib/zipstream/src/File.php
@@ -18,7 +18,7 @@ use ZipStream\Exception\StreamNotSeekableException;
/**
* @internal
*/
-class File
+final class File
{
private const CHUNKED_READ_BLOCK_SIZE = 0x1000000;
diff --git a/public/lib/zipstream/src/OperationMode.php b/public/lib/zipstream/src/OperationMode.php
index dd650f0702f..ae45e3186fc 100644
--- a/public/lib/zipstream/src/OperationMode.php
+++ b/public/lib/zipstream/src/OperationMode.php
@@ -6,6 +6,8 @@ namespace ZipStream;
/**
* ZipStream execution operation modes
+ *
+ * @api
*/
enum OperationMode
{
diff --git a/public/lib/zipstream/src/PackField.php b/public/lib/zipstream/src/PackField.php
index 892b4009a62..bf7834035d5 100644
--- a/public/lib/zipstream/src/PackField.php
+++ b/public/lib/zipstream/src/PackField.php
@@ -10,7 +10,7 @@ use RuntimeException;
* @internal
* TODO: Make class readonly when requiring PHP 8.2 exclusively
*/
-class PackField
+final class PackField
{
public const MAX_V = 0xFFFFFFFF;
diff --git a/public/lib/zipstream/src/Stream/CallbackStreamWrapper.php b/public/lib/zipstream/src/Stream/CallbackStreamWrapper.php
new file mode 100644
index 00000000000..fb354879cd4
--- /dev/null
+++ b/public/lib/zipstream/src/Stream/CallbackStreamWrapper.php
@@ -0,0 +1,253 @@
+ Map of stream IDs to callback functions */
+ private static array $callbacks = [];
+
+ /** @var string|null Unique identifier for this stream instance */
+ private ?string $id = null;
+
+ /** @var int Current position in the stream */
+ private int $pos = 0;
+
+ /**
+ * Destructor - ensures cleanup even if stream_close() isn't called.
+ * Prevents memory leaks in long-running processes.
+ */
+ public function __destruct()
+ {
+ $this->stream_close();
+ }
+
+ /**
+ * Create a new callback stream.
+ *
+ * @param callable(string):void $callback Function to call with written data
+ * @return resource|false Stream resource or false on failure
+ */
+ public static function open(callable $callback)
+ {
+ if (!in_array(self::PROTOCOL, stream_get_wrappers(), true)) {
+ if (!stream_wrapper_register(self::PROTOCOL, self::class)) {
+ return false;
+ }
+ }
+
+ // Generate cryptographically secure unique ID to prevent collisions
+ $id = 'cb_' . bin2hex(random_bytes(16));
+ self::$callbacks[$id] = $callback;
+
+ return fopen(self::PROTOCOL . "://{$id}", 'wb');
+ }
+
+ /**
+ * Clean up all registered callbacks (useful for testing).
+ *
+ * @internal
+ */
+ public static function cleanup(): void
+ {
+ self::$callbacks = [];
+ }
+
+ /**
+ * Open the stream.
+ *
+ * @param string $path Stream path containing the callback ID
+ * @param string $mode File mode (must contain 'w' for writing)
+ * @param int $options Stream options (required by interface, unused)
+ * @param string|null $opened_path Opened path reference (required by interface, unused)
+ * @return bool True if stream opened successfully
+ * @psalm-suppress UnusedParam $options and $opened_path are required by the stream wrapper interface
+ */
+ public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
+ {
+ if (!str_contains($mode, 'w')) {
+ return false;
+ }
+
+ $host = parse_url($path, PHP_URL_HOST);
+ if ($host === false || $host === null) {
+ return false;
+ }
+
+ $this->id = $host;
+ return isset(self::$callbacks[$this->id]);
+ }
+
+ /**
+ * Write data to the callback.
+ *
+ * @param string $data Data to write
+ * @return int Number of bytes written
+ * @throws RuntimeException If callback execution fails
+ */
+ public function stream_write(string $data): int
+ {
+ if ($this->id === null) {
+ trigger_error('Stream not properly initialized', E_USER_WARNING);
+ return 0;
+ }
+
+ $callback = self::$callbacks[$this->id] ?? null;
+ if ($callback === null) {
+ trigger_error('Callback not found for stream', E_USER_WARNING);
+ return 0;
+ }
+
+ try {
+ $callback($data);
+ } catch (Throwable $e) {
+ throw new RuntimeException(
+ 'Callback function failed during stream write: ' . $e->getMessage(),
+ 0,
+ $e
+ );
+ }
+
+ $length = strlen($data);
+ $this->pos += $length;
+ return $length;
+ }
+
+ /**
+ * Get current position in stream.
+ *
+ * @return int Current position
+ */
+ public function stream_tell(): int
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Check if stream has reached end of file.
+ *
+ * @return bool Always false for write-only streams
+ */
+ public function stream_eof(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Flush stream buffers.
+ *
+ * @return bool Always true (no buffering)
+ */
+ public function stream_flush(): bool
+ {
+ return true;
+ }
+
+ /**
+ * Close the stream and clean up callback.
+ */
+ public function stream_close(): void
+ {
+ if ($this->id !== null) {
+ unset(self::$callbacks[$this->id]);
+ $this->id = null;
+ }
+ }
+
+ /**
+ * Get stream statistics.
+ *
+ * @return array Stream statistics
+ */
+ public function stream_stat(): array
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0o100666, // Regular file, read/write permissions
+ 'nlink' => 1,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->pos,
+ 'atime' => time(),
+ 'mtime' => time(),
+ 'ctime' => time(),
+ 'blksize' => 4096,
+ 'blocks' => ceil($this->pos / 4096),
+ ];
+ }
+
+ /**
+ * Read data from stream (not supported - write-only stream).
+ *
+ * @param int $count Number of bytes to read (required by interface, unused)
+ * @return string Always empty string
+ * @psalm-suppress UnusedParam $count is required by the stream wrapper interface
+ */
+ public function stream_read(int $count): string
+ {
+ trigger_error('Read operations not supported on callback streams', E_USER_WARNING);
+ return '';
+ }
+
+ /**
+ * Seek to position in stream (not supported).
+ *
+ * @param int $offset Offset to seek to (required by interface, unused)
+ * @param int $whence Seek mode (required by interface, unused)
+ * @return bool Always false
+ * @psalm-suppress UnusedParam $offset and $whence are required by the stream wrapper interface
+ */
+ public function stream_seek(int $offset, int $whence = SEEK_SET): bool
+ {
+ trigger_error('Seek operations not supported on callback streams', E_USER_WARNING);
+ return false;
+ }
+
+ /**
+ * Set options on stream (not supported).
+ *
+ * @param int $option Option to set (required by interface, unused)
+ * @param int $arg1 First argument (required by interface, unused)
+ * @param int $arg2 Second argument (required by interface, unused)
+ * @return bool Always false
+ * @psalm-suppress UnusedParam All parameters are required by the stream wrapper interface
+ */
+ public function stream_set_option(int $option, int $arg1, int $arg2): bool
+ {
+ return false;
+ }
+
+ /**
+ * Truncate stream (not supported).
+ *
+ * @param int $new_size New size (required by interface, unused)
+ * @return bool Always false
+ * @psalm-suppress UnusedParam $new_size is required by the stream wrapper interface
+ */
+ public function stream_truncate(int $new_size): bool
+ {
+ trigger_error('Truncate operations not supported on callback streams', E_USER_WARNING);
+ return false;
+ }
+}
diff --git a/public/lib/zipstream/src/Version.php b/public/lib/zipstream/src/Version.php
index c014f8a104b..483d25a5870 100644
--- a/public/lib/zipstream/src/Version.php
+++ b/public/lib/zipstream/src/Version.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace ZipStream;
+/**
+ * @api
+ */
enum Version: int
{
case STORE = 0x000A; // 1.00
diff --git a/public/lib/zipstream/src/ZipStream.php b/public/lib/zipstream/src/ZipStream.php
index 698ffbb32be..d7b939dddb7 100644
--- a/public/lib/zipstream/src/ZipStream.php
+++ b/public/lib/zipstream/src/ZipStream.php
@@ -64,6 +64,8 @@ use ZipStream\Exception\ResourceActionException;
* // write archive footer to stream
* $zip->finish();
* ```
+ *
+ * @api
*/
class ZipStream
{
@@ -691,7 +693,7 @@ class ZipStream
/**
* Write zip footer to stream.
*
- * The clase is left in an unusable state after `finish`.
+ * The class is left in an unusable state after `finish`.
*
* ##### Example
*