Merge branch 'MDL-86467-main' of https://github.com/yusufwib01/moodle
This commit is contained in:
@@ -611,7 +611,7 @@ All rights reserved.</copyright>
|
||||
<location>zipstream</location>
|
||||
<name>ZipStream-PHP</name>
|
||||
<description>PHP ZIP Streaming Library</description>
|
||||
<version>3.1.2</version>
|
||||
<version>3.2.0</version>
|
||||
<license>MIT</license>
|
||||
<repository>https://github.com/maennchen/ZipStream-PHP</repository>
|
||||
<copyrights>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
enum CompressionMethod: int
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -4,4 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
abstract class Exception extends \Exception {}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@ use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a file wasn't found
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class FileNotFoundException extends Exception
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@ use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a counter value exceeds storage size
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class OverflowException extends Exception
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@ use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a resource like `fread` returns false
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ResourceActionException extends Exception
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -8,6 +8,8 @@ use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a stream can't be read.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class StreamNotReadableException extends Exception
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ use ZipStream\Exception\StreamNotSeekableException;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class File
|
||||
final class File
|
||||
{
|
||||
private const CHUNKED_READ_BLOCK_SIZE = 0x1000000;
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace ZipStream;
|
||||
|
||||
/**
|
||||
* ZipStream execution operation modes
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
enum OperationMode
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Stream;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Stream wrapper that allows writing data to a callback function.
|
||||
*
|
||||
* This wrapper creates a virtual stream that forwards all written data
|
||||
* to a provided callback function, enabling custom output handling
|
||||
* such as streaming to HTTP responses, files, or other destinations.
|
||||
*
|
||||
* @psalm-suppress UnusedClass Used dynamically through stream_wrapper_register
|
||||
*/
|
||||
final class CallbackStreamWrapper
|
||||
{
|
||||
public const PROTOCOL = 'zipcb';
|
||||
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/** @var array<string, callable(string):void> 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<string, mixed> 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;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
enum Version: int
|
||||
{
|
||||
case STORE = 0x000A; // 1.00
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user