generated from jric11/baseProject
Codex's updates...
This commit is contained in:
@@ -71,4 +71,5 @@ return $config->setRules([
|
||||
])
|
||||
->setFinder($finder)
|
||||
->setRiskyAllowed(true)
|
||||
->setUnsupportedPhpVersionAllowed(true)
|
||||
->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect());
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
php 8.4.10
|
||||
php 8.5.0
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@
|
||||
"ext-zip": "*",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"php-coveralls/php-coveralls": "^2.5",
|
||||
"friendsofphp/php-cs-fixer": "^3.16",
|
||||
"friendsofphp/php-cs-fixer": "^3.86",
|
||||
"vimeo/psalm": "^6.0",
|
||||
"brianium/paratest": "^7.7"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
Compressing large files to S3 compatible storages
|
||||
=============
|
||||
|
||||
S3 compatible storages usually have a limitation of 5 GiB when using single upload. When generating larger zips, the
|
||||
solution is to use a `multi-part upload`_.
|
||||
|
||||
.. _multi-part upload: <https://github.com/awsdocs/aws-doc-sdk-examples/blob/75a8bf8536d436db91ec5de1ba0ed80fd258e904/php/example_code/s3/s3-multipart-upload-using-lowlevel-php-sdk-api.php>`
|
||||
|
||||
We can implement a `PSR-7 stream <https://www.php-fig.org/psr/psr-7/>`_ that buffers ZipStream's output and uploads
|
||||
to S3 in chunks.
|
||||
|
||||
MultipartUploadBufferStream example
|
||||
---------------
|
||||
|
||||
.. code-block:: php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Aws\S3\S3ClientInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
use function strlen;
|
||||
use function substr;
|
||||
|
||||
final class MultipartUploadBufferStream implements StreamInterface
|
||||
{
|
||||
private const int PART_SIZE = 5242880; // 5 MiB in bytes
|
||||
|
||||
private string $buffer = '';
|
||||
private int $bufferSize = 0;
|
||||
private int $partNumber = 1;
|
||||
private array $parts = [];
|
||||
|
||||
private string $uploadId;
|
||||
|
||||
public function __construct(
|
||||
private readonly string $destinationFileName,
|
||||
private readonly string $bucket,
|
||||
private readonly S3ClientInterface $client
|
||||
) {
|
||||
$result = $this->client->createMultipartUpload(
|
||||
[
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $destinationFileName,
|
||||
'StorageClass' => 'REDUCED_REDUNDANCY',
|
||||
]
|
||||
);
|
||||
|
||||
$this->uploadId = $result['UploadId'];
|
||||
|
||||
$this->parts['Parts'] = [];
|
||||
}
|
||||
|
||||
public function write($string): int
|
||||
{
|
||||
$chunkSize = strlen($string);
|
||||
|
||||
$this->buffer .= $string;
|
||||
$this->bufferSize += $chunkSize;
|
||||
|
||||
if ($this->bufferSize >= self::PART_SIZE) {
|
||||
$this->uploadPart();
|
||||
}
|
||||
|
||||
return $chunkSize;
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
// Upload remaining closing bytes from zip
|
||||
$this->uploadPart();
|
||||
|
||||
$this->client->completeMultipartUpload([
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $this->destinationFileName,
|
||||
'UploadId' => $this->uploadId,
|
||||
'MultipartUpload' => $this->parts,
|
||||
]);
|
||||
|
||||
$this->buffer = '';
|
||||
$this->bufferSize = 0;
|
||||
}
|
||||
|
||||
private function uploadPart(): void
|
||||
{
|
||||
$result = $this->client->uploadPart([
|
||||
'Bucket' => $this->bucket,
|
||||
'Key' => $this->destinationFileName,
|
||||
'UploadId' => $this->uploadId,
|
||||
'PartNumber' => $this->partNumber,
|
||||
'Body' => $this->buffer,
|
||||
]);
|
||||
|
||||
$this->buffer = '';
|
||||
$this->bufferSize = 0;
|
||||
|
||||
$this->parts['Parts'][$this->partNumber] = [
|
||||
'PartNumber' => $this->partNumber,
|
||||
'ETag' => $result['ETag'],
|
||||
];
|
||||
|
||||
$this->partNumber++;
|
||||
|
||||
$result = null;
|
||||
gc_collect_cycles(); // To avoid memory leaks. @see github.com/aws/aws-sdk-php/issues/1273
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->getContents();
|
||||
}
|
||||
|
||||
public function getContents(): string
|
||||
{
|
||||
$buffer = $this->buffer;
|
||||
$this->buffer = '';
|
||||
$this->bufferSize = 0;
|
||||
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
public function detach(): null
|
||||
{
|
||||
$this->close();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getSize(): int
|
||||
{
|
||||
return $this->bufferSize;
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
{
|
||||
throw new \RuntimeException('Cannot seek a BufferStream');
|
||||
}
|
||||
|
||||
public function eof(): bool
|
||||
{
|
||||
return $this->bufferSize === 0;
|
||||
}
|
||||
|
||||
public function tell(): int
|
||||
{
|
||||
throw new \RuntimeException('Cannot determine the position of a BufferStream');
|
||||
}
|
||||
|
||||
public function read($length): string
|
||||
{
|
||||
$currentLength = $this->bufferSize;
|
||||
|
||||
if ($length >= $currentLength) {
|
||||
// No need to slice the buffer because we don't have enough data.
|
||||
$result = $this->buffer;
|
||||
$this->buffer = '';
|
||||
$this->bufferSize = 0;
|
||||
} else {
|
||||
// Slice up the result to provide a subset of the buffer.
|
||||
$result = substr($this->buffer, 0, $length);
|
||||
$this->buffer = substr($this->buffer, $length);
|
||||
$this->bufferSize -= $length;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
return $key ? null : [];
|
||||
}
|
||||
}
|
||||
|
||||
Then, we can use it with ZipStream to compress the files and upload the parts to the storage.
|
||||
|
||||
ZipStream usage
|
||||
---------------
|
||||
|
||||
.. code-block:: php
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Credentials\CredentialProvider;
|
||||
use MultipartUploadBufferStream;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
$bucket = 'your bucket name';
|
||||
$client = new S3Client([
|
||||
'region' => 'your region',
|
||||
'version' => 'latest',
|
||||
'bucketName' => $bucket,
|
||||
'credentials' => CredentialProvider::defaultProvider(),
|
||||
]);
|
||||
|
||||
$bufferStream = new MultipartUploadBufferStream(
|
||||
'destination-file.zip',
|
||||
$bucket,
|
||||
$client,
|
||||
);
|
||||
|
||||
$zip = new ZipStream(
|
||||
outputStream: $destination,
|
||||
defaultCompressionMethod: CompressionMethod::STORE,
|
||||
defaultEnableZeroHeader: true,
|
||||
sendHttpHeaders: false,
|
||||
);
|
||||
|
||||
$zip->addFile(
|
||||
fileName: 'big-file-1.txt',
|
||||
data: 'File1 data',
|
||||
);
|
||||
$zip->addFile(
|
||||
fileName: 'big-file-2.txt',
|
||||
data: 'File2 data',
|
||||
);
|
||||
|
||||
$zip->finish();
|
||||
$destination->close(); // Needed after $zip->finish() to upload the last remaining bytes to S3
|
||||
|
||||
You can read more about the logic behind this implementation in the `discussion`_.
|
||||
|
||||
.. _discussion: https://github.com/maennchen/ZipStream-PHP/discussions/402
|
||||
@@ -16,6 +16,7 @@ user, which is much faster. It can work with S3 buckets or any PSR7 Stream.
|
||||
Nginx
|
||||
Varnish
|
||||
ContentLength
|
||||
LargeFilesToS3
|
||||
|
||||
Installation
|
||||
---------------
|
||||
|
||||
+9
-9
@@ -110,8 +110,8 @@ final class File
|
||||
$detectedSize = $forecastSize ?? ($this->compressedSize > 0 ? $this->compressedSize : null);
|
||||
|
||||
if (
|
||||
$this->isSimulation() &&
|
||||
$detectedSize !== null
|
||||
$this->isSimulation()
|
||||
&& $detectedSize !== null
|
||||
) {
|
||||
$this->uncompressedSize = $detectedSize;
|
||||
$this->compressedSize = $detectedSize;
|
||||
@@ -202,10 +202,10 @@ final class File
|
||||
compressionMethod: $this->compressionMethod,
|
||||
lastModificationDateTime: $this->lastModificationDateTime,
|
||||
crc32UncompressedData: $this->crc,
|
||||
compressedSize: $zip64Enabled
|
||||
compressedSize: ($forceEnableZip64 || $this->compressedSize > 0xFFFFFFFF)
|
||||
? 0xFFFFFFFF
|
||||
: $this->compressedSize,
|
||||
uncompressedSize: $zip64Enabled
|
||||
uncompressedSize: ($forceEnableZip64 || $this->uncompressedSize > 0xFFFFFFFF)
|
||||
? 0xFFFFFFFF
|
||||
: $this->uncompressedSize,
|
||||
fileName: $this->fileName,
|
||||
@@ -238,8 +238,8 @@ final class File
|
||||
// Sets Bit 11: Language encoding flag (EFS). If this bit is set,
|
||||
// the filename and comment fields for this file
|
||||
// MUST be encoded using UTF-8. (see APPENDIX D)
|
||||
if (mb_check_encoding($this->fileName, 'UTF-8') &&
|
||||
mb_check_encoding($this->comment, 'UTF-8')) {
|
||||
if (mb_check_encoding($this->fileName, 'UTF-8')
|
||||
&& mb_check_encoding($this->comment, 'UTF-8')) {
|
||||
$this->generalPurposeBitFlag |= GeneralPurposeBitFlag::EFS;
|
||||
}
|
||||
}
|
||||
@@ -321,9 +321,9 @@ final class File
|
||||
$deflate = $this->compressionInit();
|
||||
|
||||
while (
|
||||
!feof($this->unpackStream()) &&
|
||||
($this->maxSize === null || $this->uncompressedSize < $this->maxSize) &&
|
||||
($this->exactSize === null || $this->uncompressedSize < $this->exactSize)
|
||||
!feof($this->unpackStream())
|
||||
&& ($this->maxSize === null || $this->uncompressedSize < $this->maxSize)
|
||||
&& ($this->exactSize === null || $this->uncompressedSize < $this->exactSize)
|
||||
) {
|
||||
$readLength = min(
|
||||
($this->maxSize ?? PHP_INT_MAX) - $this->uncompressedSize,
|
||||
|
||||
+6
-6
@@ -29,11 +29,11 @@ abstract class Time
|
||||
[$year, $month, $day, $hour, $minute, $second] = explode(' ', $dateTime->format('Y n j G i s'));
|
||||
|
||||
return
|
||||
((int) $year << 25) |
|
||||
((int) $month << 21) |
|
||||
((int) $day << 16) |
|
||||
((int) $hour << 11) |
|
||||
((int) $minute << 5) |
|
||||
((int) $second >> 1);
|
||||
((int) $year << 25)
|
||||
| ((int) $month << 21)
|
||||
| ((int) $day << 16)
|
||||
| ((int) $hour << 11)
|
||||
| ((int) $minute << 5)
|
||||
| ((int) $second >> 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,10 @@ abstract class ExtendedInformationExtraField
|
||||
new PackField(format: 'v', value: self::TAG),
|
||||
new PackField(
|
||||
format: 'v',
|
||||
value: ($originalSize === null ? 0 : 8) +
|
||||
($compressedSize === null ? 0 : 8) +
|
||||
($relativeHeaderOffset === null ? 0 : 8) +
|
||||
($diskStartNumber === null ? 0 : 4)
|
||||
value: ($originalSize === null ? 0 : 8)
|
||||
+ ($compressedSize === null ? 0 : 8)
|
||||
+ ($relativeHeaderOffset === null ? 0 : 8)
|
||||
+ ($diskStartNumber === null ? 0 : 4)
|
||||
),
|
||||
...($originalSize === null ? [] : [
|
||||
new PackField(format: 'P', value: $originalSize),
|
||||
|
||||
+3
-3
@@ -714,9 +714,9 @@ class ZipStream
|
||||
}
|
||||
|
||||
// Add 64bit headers (if applicable)
|
||||
if (count($this->centralDirectoryRecords) >= 0xFFFF ||
|
||||
$centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF ||
|
||||
$sizeOfCentralDirectory > 0xFFFFFFFF) {
|
||||
if (count($this->centralDirectoryRecords) >= 0xFFFF
|
||||
|| $centralDirectoryStartOffsetOnDisk > 0xFFFFFFFF
|
||||
|| $sizeOfCentralDirectory > 0xFFFFFFFF) {
|
||||
if (!$this->enableZip64) {
|
||||
throw new OverflowException();
|
||||
}
|
||||
|
||||
+15
-6
@@ -24,7 +24,9 @@ final class CallbackOutputTest extends TestCase
|
||||
$buf = '';
|
||||
$zip = new ZipStream(
|
||||
outputStream: CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$buf): void { $buf .= $chunk; }
|
||||
static function (string $chunk) use (&$buf): void {
|
||||
$buf .= $chunk;
|
||||
}
|
||||
),
|
||||
sendHttpHeaders: false
|
||||
);
|
||||
@@ -53,10 +55,14 @@ final class CallbackOutputTest extends TestCase
|
||||
$buf2 = '';
|
||||
|
||||
$stream1 = CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$buf1): void { $buf1 .= $chunk; }
|
||||
static function (string $chunk) use (&$buf1): void {
|
||||
$buf1 .= $chunk;
|
||||
}
|
||||
);
|
||||
$stream2 = CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$buf2): void { $buf2 .= $chunk; }
|
||||
static function (string $chunk) use (&$buf2): void {
|
||||
$buf2 .= $chunk;
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertIsResource($stream1);
|
||||
@@ -112,7 +118,8 @@ final class CallbackOutputTest extends TestCase
|
||||
public function testStreamPositionTracking(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void { /* no-op */ }
|
||||
static function (string $chunk): void { // no-op
|
||||
}
|
||||
);
|
||||
|
||||
$this->assertSame(0, ftell($stream));
|
||||
@@ -129,7 +136,8 @@ final class CallbackOutputTest extends TestCase
|
||||
public function testInvalidModeRejection(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void { /* no-op */ }
|
||||
static function (string $chunk): void { // no-op
|
||||
}
|
||||
);
|
||||
|
||||
// Close the stream first
|
||||
@@ -143,7 +151,8 @@ final class CallbackOutputTest extends TestCase
|
||||
public function testStreamStatistics(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void { /* no-op */ }
|
||||
static function (string $chunk): void { // no-op
|
||||
}
|
||||
);
|
||||
|
||||
fwrite($stream, 'test data');
|
||||
|
||||
@@ -35,26 +35,26 @@ class CentralDirectoryFileHeaderTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($header),
|
||||
'504b0102' . // 4 bytes; central file header signature
|
||||
'0306' . // 2 bytes; version made by
|
||||
'2d00' . // 2 bytes; version needed to extract
|
||||
'2222' . // 2 bytes; general purpose bit flag
|
||||
'0800' . // 2 bytes; compression method
|
||||
'2008' . // 2 bytes; last mod file time
|
||||
'2154' . // 2 bytes; last mod file date
|
||||
'11111111' . // 4 bytes; crc-32
|
||||
'77777777' . // 4 bytes; compressed size
|
||||
'99999999' . // 4 bytes; uncompressed size
|
||||
'0800' . // 2 bytes; file name length (n)
|
||||
'0c00' . // 2 bytes; extra field length (m)
|
||||
'0c00' . // 2 bytes; file comment length (o)
|
||||
'0000' . // 2 bytes; disk number start
|
||||
'0000' . // 2 bytes; internal file attributes
|
||||
'20000000' . // 4 bytes; external file attributes
|
||||
'34120000' . // 4 bytes; relative offset of local header
|
||||
'746573742e706e67' . // n bytes; file name
|
||||
'736f6d6520636f6e74656e74' . // m bytes; extra field
|
||||
'736f6d6520636f6d6d656e74' // o bytes; file comment
|
||||
'504b0102' // 4 bytes; central file header signature
|
||||
. '0306' // 2 bytes; version made by
|
||||
. '2d00' // 2 bytes; version needed to extract
|
||||
. '2222' // 2 bytes; general purpose bit flag
|
||||
. '0800' // 2 bytes; compression method
|
||||
. '2008' // 2 bytes; last mod file time
|
||||
. '2154' // 2 bytes; last mod file date
|
||||
. '11111111' // 4 bytes; crc-32
|
||||
. '77777777' // 4 bytes; compressed size
|
||||
. '99999999' // 4 bytes; uncompressed size
|
||||
. '0800' // 2 bytes; file name length (n)
|
||||
. '0c00' // 2 bytes; extra field length (m)
|
||||
. '0c00' // 2 bytes; file comment length (o)
|
||||
. '0000' // 2 bytes; disk number start
|
||||
. '0000' // 2 bytes; internal file attributes
|
||||
. '20000000' // 4 bytes; external file attributes
|
||||
. '34120000' // 4 bytes; relative offset of local header
|
||||
. '746573742e706e67' // n bytes; file name
|
||||
. '736f6d6520636f6e74656e74' // m bytes; extra field
|
||||
. '736f6d6520636f6d6d656e74' // o bytes; file comment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ class DataDescriptorTest extends TestCase
|
||||
compressedSize: 0x77777777,
|
||||
uncompressedSize: 0x99999999,
|
||||
)),
|
||||
'504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
|
||||
'11111111' . // 4 bytes; CRC-32 of uncompressed data
|
||||
'77777777' . // 4 bytes; Compressed size
|
||||
'99999999' // 4 bytes; Uncompressed size
|
||||
'504b0708' // 4 bytes; Optional data descriptor signature = 0x08074b50
|
||||
. '11111111' // 4 bytes; CRC-32 of uncompressed data
|
||||
. '77777777' // 4 bytes; Compressed size
|
||||
. '99999999' // 4 bytes; Uncompressed size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,15 +21,15 @@ class EndOfCentralDirectoryTest extends TestCase
|
||||
centralDirectoryStartOffsetOnDisk: 0x33,
|
||||
zipFileComment: 'foo',
|
||||
)),
|
||||
'504b0506' . // 4 bytes; end of central dir signature 0x06054b50
|
||||
'0000' . // 2 bytes; number of this disk
|
||||
'0000' . // 2 bytes; number of the disk with the start of the central directory
|
||||
'1000' . // 2 bytes; total number of entries in the central directory on this disk
|
||||
'1000' . // 2 bytes; total number of entries in the central directory
|
||||
'22000000' . // 4 bytes; size of the central directory
|
||||
'33000000' . // 4 bytes; offset of start of central directory with respect to the starting disk number
|
||||
'0300' . // 2 bytes; .ZIP file comment length
|
||||
bin2hex('foo')
|
||||
'504b0506' // 4 bytes; end of central dir signature 0x06054b50
|
||||
. '0000' // 2 bytes; number of this disk
|
||||
. '0000' // 2 bytes; number of the disk with the start of the central directory
|
||||
. '1000' // 2 bytes; total number of entries in the central directory on this disk
|
||||
. '1000' // 2 bytes; total number of entries in the central directory
|
||||
. '22000000' // 4 bytes; size of the central directory
|
||||
. '33000000' // 4 bytes; offset of start of central directory with respect to the starting disk number
|
||||
. '0300' // 2 bytes; .ZIP file comment length
|
||||
. bin2hex('foo')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class EndlessCycleStream implements StreamInterface
|
||||
throw new RuntimeException('Infinite Stream!');
|
||||
}
|
||||
|
||||
public function getMetadata(?string $key = null): array|null
|
||||
public function getMetadata(?string $key = null): ?array
|
||||
{
|
||||
return $key !== null ? null : [];
|
||||
}
|
||||
|
||||
+13
-13
@@ -29,19 +29,19 @@ class LocalFileHeaderTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex((string) $header),
|
||||
'504b0304' . // 4 bytes; Local file header signature
|
||||
'2d00' . // 2 bytes; Version needed to extract (minimum)
|
||||
'2222' . // 2 bytes; General purpose bit flag
|
||||
'0800' . // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8
|
||||
'2008' . // 2 bytes; File last modification time
|
||||
'2154' . // 2 bytes; File last modification date
|
||||
'11111111' . // 4 bytes; CRC-32 of uncompressed data
|
||||
'77777777' . // 4 bytes; Compressed size (or 0xffffffff for ZIP64)
|
||||
'99999999' . // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64)
|
||||
'0800' . // 2 bytes; File name length (n)
|
||||
'0c00' . // 2 bytes; Extra field length (m)
|
||||
'746573742e706e67' . // n bytes; File name
|
||||
'736f6d6520636f6e74656e74' // m bytes; Extra field
|
||||
'504b0304' // 4 bytes; Local file header signature
|
||||
. '2d00' // 2 bytes; Version needed to extract (minimum)
|
||||
. '2222' // 2 bytes; General purpose bit flag
|
||||
. '0800' // 2 bytes; Compression method; e.g. none = 0, DEFLATE = 8
|
||||
. '2008' // 2 bytes; File last modification time
|
||||
. '2154' // 2 bytes; File last modification date
|
||||
. '11111111' // 4 bytes; CRC-32 of uncompressed data
|
||||
. '77777777' // 4 bytes; Compressed size (or 0xffffffff for ZIP64)
|
||||
. '99999999' // 4 bytes; Uncompressed size (or 0xffffffff for ZIP64)
|
||||
. '0800' // 2 bytes; File name length (n)
|
||||
. '0c00' // 2 bytes; Extra field length (m)
|
||||
. '746573742e706e67' // n bytes; File name
|
||||
. '736f6d6520636f6e74656e74' // m bytes; Extra field
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ namespace ZipStream\Test;
|
||||
|
||||
trait Tempfile
|
||||
{
|
||||
protected string|null $tempfile;
|
||||
protected ?string $tempfile;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
|
||||
@@ -19,10 +19,10 @@ class DataDescriptorTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($descriptor),
|
||||
'504b0708' . // 4 bytes; Optional data descriptor signature = 0x08074b50
|
||||
'11111111' . // 4 bytes; CRC-32 of uncompressed data
|
||||
'6666666677777777' . // 8 bytes; Compressed size
|
||||
'8888888899999999' // 8 bytes; Uncompressed size
|
||||
'504b0708' // 4 bytes; Optional data descriptor signature = 0x08074b50
|
||||
. '11111111' // 4 bytes; CRC-32 of uncompressed data
|
||||
. '6666666677777777' // 8 bytes; Compressed size
|
||||
. '8888888899999999' // 8 bytes; Uncompressed size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -19,10 +19,10 @@ class EndOfCentralDirectoryLocatorTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($descriptor),
|
||||
'504b0607' . // 4 bytes; zip64 end of central dir locator signature - 0x07064b50
|
||||
'11111111' . // 4 bytes; number of the disk with the start of the zip64 end of central directory
|
||||
'3333333322222222' . // 28 bytes; relative offset of the zip64 end of central directory record
|
||||
'44444444' // 4 bytes;total number of disks
|
||||
'504b0607' // 4 bytes; zip64 end of central dir locator signature - 0x07064b50
|
||||
. '11111111' // 4 bytes; number of the disk with the start of the zip64 end of central directory
|
||||
. '3333333322222222' // 28 bytes; relative offset of the zip64 end of central directory record
|
||||
. '44444444' // 4 bytes;total number of disks
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,17 +25,17 @@ class EndOfCentralDirectoryTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($descriptor),
|
||||
'504b0606' . // 4 bytes;zip64 end of central dir signature - 0x06064b50
|
||||
'2f00000000000000' . // 8 bytes; size of zip64 end of central directory record
|
||||
'3333' . // 2 bytes; version made by
|
||||
'4444' . // 2 bytes; version needed to extract
|
||||
'55555555' . // 4 bytes; number of this disk
|
||||
'66666666' . // 4 bytes; number of the disk with the start of the central directory
|
||||
'8888888877777777' . // 8 bytes; total number of entries in the central directory on this disk
|
||||
'aaaaaaaa99999999' . // 8 bytes; total number of entries in the central directory
|
||||
'ccccccccbbbbbbbb' . // 8 bytes; size of the central directory
|
||||
'eeeeeeeedddddddd' . // 8 bytes; offset of start of central directory with respect to the starting disk number
|
||||
bin2hex('foo')
|
||||
'504b0606' // 4 bytes;zip64 end of central dir signature - 0x06064b50
|
||||
. '2f00000000000000' // 8 bytes; size of zip64 end of central directory record
|
||||
. '3333' // 2 bytes; version made by
|
||||
. '4444' // 2 bytes; version needed to extract
|
||||
. '55555555' // 4 bytes; number of this disk
|
||||
. '66666666' // 4 bytes; number of the disk with the start of the central directory
|
||||
. '8888888877777777' // 8 bytes; total number of entries in the central directory on this disk
|
||||
. 'aaaaaaaa99999999' // 8 bytes; total number of entries in the central directory
|
||||
. 'ccccccccbbbbbbbb' // 8 bytes; size of the central directory
|
||||
. 'eeeeeeeedddddddd' // 8 bytes; offset of start of central directory with respect to the starting disk number
|
||||
. bin2hex('foo')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -20,12 +20,12 @@ class ExtendedInformationExtraFieldTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($extraField),
|
||||
'0100' . // 2 bytes; Tag for this "extra" block type
|
||||
'1c00' . // 2 bytes; Size of this "extra" block
|
||||
'6666666677777777' . // 8 bytes; Original uncompressed file size
|
||||
'8888888899999999' . // 8 bytes; Size of compressed data
|
||||
'1111111122222222' . // 8 bytes; Offset of local header record
|
||||
'33333333' // 4 bytes; Number of the disk on which this file starts
|
||||
'0100' // 2 bytes; Tag for this "extra" block type
|
||||
. '1c00' // 2 bytes; Size of this "extra" block
|
||||
. '6666666677777777' // 8 bytes; Original uncompressed file size
|
||||
. '8888888899999999' // 8 bytes; Size of compressed data
|
||||
. '1111111122222222' // 8 bytes; Offset of local header record
|
||||
. '33333333' // 4 bytes; Number of the disk on which this file starts
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ class ExtendedInformationExtraFieldTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($extraField),
|
||||
'0100' . // 2 bytes; Tag for this "extra" block type
|
||||
'0000' // 2 bytes; Size of this "extra" block
|
||||
'0100' // 2 bytes; Tag for this "extra" block type
|
||||
. '0000' // 2 bytes; Size of this "extra" block
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-9
@@ -62,10 +62,10 @@ class ZipStreamTest extends TestCase
|
||||
|
||||
$name = 'árvíztűrő tükörfúrógép.txt';
|
||||
$content = 'Sample String Data';
|
||||
$comment =
|
||||
'Filename has every special characters ' .
|
||||
'from Hungarian language in lowercase. ' .
|
||||
'In uppercase: ÁÍŰŐÜÖÚÓÉ';
|
||||
$comment
|
||||
= 'Filename has every special characters '
|
||||
. 'from Hungarian language in lowercase. '
|
||||
. 'In uppercase: ÁÍŰŐÜÖÚÓÉ';
|
||||
|
||||
$zip->addFile(fileName: $name, data: $content, comment: $comment);
|
||||
$zip->finish();
|
||||
@@ -327,11 +327,14 @@ class ZipStreamTest extends TestCase
|
||||
defaultEnableZeroHeader: false,
|
||||
);
|
||||
|
||||
if (file_exists('/dev/null')) {
|
||||
$streamUnseekable = fopen('/dev/null', 'w+');
|
||||
} elseif (file_exists('NUL')) {
|
||||
$streamUnseekable = fopen('NUL', 'w+');
|
||||
} else {
|
||||
foreach (['cat', 'more'] as $cmd) {
|
||||
$streamUnseekable = @popen($cmd, 'w');
|
||||
if ($streamUnseekable !== false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($streamUnseekable === false) {
|
||||
$this->markTestSkipped('Needs file /dev/null');
|
||||
}
|
||||
|
||||
@@ -546,12 +549,24 @@ class ZipStreamTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
$zip->addDirectory('mydir/');
|
||||
|
||||
$zip->finish();
|
||||
|
||||
$tmpDir = $this->validateAndExtractZip($this->tempfile);
|
||||
|
||||
$files = $this->getRecursiveFileList($tmpDir);
|
||||
$this->assertSame(['sample0', 'sample1', 'sample2', 'sample3'], $files);
|
||||
|
||||
$this->assertFileDoesNotContain(
|
||||
$this->tempfile,
|
||||
PackField::pack(
|
||||
new PackField(format: 'V', value: 0x00000000), // CRC-32 = 0 (directory)
|
||||
new PackField(format: 'V', value: 0xFFFFFFFF), // compressedSize — bug sentinel
|
||||
new PackField(format: 'V', value: 0xFFFFFFFF), // uncompressedSize — bug sentinel
|
||||
new PackField(format: 'v', value: 6), // filename length = strlen('mydir/')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[Group('slow')]
|
||||
|
||||
+2
-2
@@ -15,8 +15,8 @@ class ExtendedInformationExtraFieldTest extends TestCase
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex((string) $extraField),
|
||||
'5356' . // 2 bytes; Tag for this "extra" block type
|
||||
'0000' // 2 bytes; TODO: Document
|
||||
'5356' // 2 bytes; Tag for this "extra" block type
|
||||
. '0000' // 2 bytes; TODO: Document
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user