mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-10-06 11:45:48 +08:00
Added simple ZIP creator
This commit is contained in:
parent
73896ae64b
commit
471628618c
3 changed files with 428 additions and 28 deletions
|
@ -124,64 +124,64 @@ trait User
|
||||||
$oZip = new \ZipArchive();
|
$oZip = new \ZipArchive();
|
||||||
$oZip->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE);
|
$oZip->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE);
|
||||||
$oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
$oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||||
|
foreach ($aData as $aItem) {
|
||||||
foreach ($aData as $aItem)
|
|
||||||
{
|
|
||||||
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
|
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
|
||||||
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
|
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
|
||||||
|
if (!empty($sFileHash)) {
|
||||||
if (!empty($sFileHash))
|
|
||||||
{
|
|
||||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||||
if (!$oZip->addFile($sFullFileNameHash, $sFileName))
|
if (!$oZip->addFile($sFullFileNameHash, $sFileName)) {
|
||||||
{
|
|
||||||
$bError = true;
|
$bError = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$bError)
|
if (!$bError) {
|
||||||
{
|
|
||||||
$bError = !$oZip->close();
|
$bError = !$oZip->close();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
$oZip->close();
|
$oZip->close();
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
} else {
|
||||||
|
@\unlink($sZipFileName);
|
||||||
|
$oZip = new \SnappyMail\Stream\ZIP($sZipFileName);
|
||||||
|
// $oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||||
|
foreach ($aData as $aItem) {
|
||||||
|
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
|
||||||
|
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
|
||||||
|
if (!empty($sFileHash)) {
|
||||||
|
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||||
|
if (!$oZip->addFile($sFullFileNameHash, $sFileName)) {
|
||||||
|
$bError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$oZip->close();
|
||||||
|
*/
|
||||||
} else {
|
} else {
|
||||||
@\unlink($sZipFileName);
|
@\unlink($sZipFileName);
|
||||||
$oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP);
|
$oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP);
|
||||||
$oZip->compressFiles(\Phar::GZ);
|
$oZip->compressFiles(\Phar::GZ);
|
||||||
|
foreach ($aData as $aItem) {
|
||||||
foreach ($aData as $aItem)
|
|
||||||
{
|
|
||||||
$sFileName = (isset($aItem['FileName']) ? (string) $aItem['FileName'] : 'file.dat');
|
$sFileName = (isset($aItem['FileName']) ? (string) $aItem['FileName'] : 'file.dat');
|
||||||
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
||||||
|
if ($sFileHash) {
|
||||||
if ($sFileHash)
|
|
||||||
{
|
|
||||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||||
$oZip->addFile($sFullFileNameHash, $sFileName);
|
$oZip->addFile($sFullFileNameHash, $sFileName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$oZip->compressFiles(\Phar::GZ);
|
$oZip->compressFiles(\Phar::GZ);
|
||||||
|
|
||||||
unset($oZip);
|
unset($oZip);
|
||||||
\rename($sZipFileName . '.zip', $sZipFileName);
|
\rename($sZipFileName . '.zip', $sZipFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($aData as $aItem)
|
foreach ($aData as $aItem) {
|
||||||
{
|
|
||||||
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
||||||
if ($sFileHash)
|
if ($sFileHash) {
|
||||||
{
|
|
||||||
$oFilesProvider->Clear($oAccount, $sFileHash);
|
$oFilesProvider->Clear($oAccount, $sFileHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$bError)
|
if (!$bError) {
|
||||||
{
|
|
||||||
$mResult = array(
|
$mResult = array(
|
||||||
'FileHash' => Utils::EncodeKeyValuesQ(array(
|
'FileHash' => Utils::EncodeKeyValuesQ(array(
|
||||||
'V' => APP_VERSION,
|
'V' => APP_VERSION,
|
||||||
|
|
|
@ -22,7 +22,7 @@ abstract class Stream
|
||||||
if ($i = \ob_get_level()) {
|
if ($i = \ob_get_level()) {
|
||||||
# Clear buffers:
|
# Clear buffers:
|
||||||
while ($i-- && \ob_end_clean());
|
while ($i-- && \ob_end_clean());
|
||||||
// if (!\ob_get_level()) \header('Content-Encoding: ');
|
// \ob_get_level() || \header('Content-Encoding: ');
|
||||||
}
|
}
|
||||||
// https://www.w3.org/TR/edge-arch/
|
// https://www.w3.org/TR/edge-arch/
|
||||||
// We just fake Drupal https://www.drupal.org/docs/8/core/modules/big-pipe/bigpipe-environment-requirements
|
// We just fake Drupal https://www.drupal.org/docs/8/core/modules/big-pipe/bigpipe-environment-requirements
|
||||||
|
|
400
snappymail/v/0.0.0/app/libraries/snappymail/stream/zip.php
Normal file
400
snappymail/v/0.0.0/app/libraries/snappymail/stream/zip.php
Normal file
|
@ -0,0 +1,400 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
Zip file creation class.
|
||||||
|
Official ZIP format: https://support.pkware.com/display/PKZIP/APPNOTE
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace SnappyMail\Stream;
|
||||||
|
|
||||||
|
class ZIP
|
||||||
|
{
|
||||||
|
const
|
||||||
|
NONE = "\x00\x00",
|
||||||
|
DEFLATE = "\x08\x00",
|
||||||
|
BZIP2 = "\x0C\x00",
|
||||||
|
LZMA = "\x0E\x00";
|
||||||
|
|
||||||
|
private
|
||||||
|
$encryption, // TODO
|
||||||
|
$compression,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Central directory
|
||||||
|
*/
|
||||||
|
$ctrl_dir = array(),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last offset position
|
||||||
|
*/
|
||||||
|
$offset = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Target resource
|
||||||
|
*/
|
||||||
|
$out = null;
|
||||||
|
|
||||||
|
function __construct($target = 'php://output', string $compression = self::DEFLATE)
|
||||||
|
{
|
||||||
|
if (\is_string($target)) {
|
||||||
|
$target = \fopen($target, 'wb');
|
||||||
|
}
|
||||||
|
if (\is_resource($target)) {
|
||||||
|
$this->out = $target;
|
||||||
|
}
|
||||||
|
if (!$this->out) {
|
||||||
|
throw new \Exception("Failed to open output: {$target}");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($compression)
|
||||||
|
{
|
||||||
|
case self::LZMA:
|
||||||
|
if (\function_exists('lzf_compress')) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case self::BZIP2:
|
||||||
|
if (\function_exists('bzcompress')) {
|
||||||
|
$compression = self::BZIP2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case self::DEFLATE:
|
||||||
|
$compression = self::DEFLATE;
|
||||||
|
break;
|
||||||
|
case self::NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$this->compression = $compression;
|
||||||
|
}
|
||||||
|
|
||||||
|
function __destruct()
|
||||||
|
{
|
||||||
|
$this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pushHttpHeaders(string $name) : void
|
||||||
|
{
|
||||||
|
if ($i = \ob_get_level()) {
|
||||||
|
# Clear buffers:
|
||||||
|
while ($i-- && \ob_end_clean());
|
||||||
|
\ob_get_level() || \header('Content-Encoding: ');
|
||||||
|
}
|
||||||
|
\header('Cache-Control: no-store, no-cache, must-revalidate');
|
||||||
|
\header('Pragma: no-cache');
|
||||||
|
\header('Content-Transfer-Encoding: binary');
|
||||||
|
$name = "{$name}.zip";
|
||||||
|
$name = \preg_match('#^[\x01-\x7F]*$#D', $name) ? $name : '=?UTF-8?B?'.\base64_encode($name).'?=';
|
||||||
|
\header("Content-Disposition: attachment; filename={$name}");
|
||||||
|
\header("Content-Type: application/zip; name={$name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close() : void
|
||||||
|
{
|
||||||
|
if ($this->out) {
|
||||||
|
// End of central directory record
|
||||||
|
$ctrldir = \implode('', $this->ctrl_dir);
|
||||||
|
$count = \pack('v', \count($this->ctrl_dir));
|
||||||
|
\fwrite($this->out, $ctrldir
|
||||||
|
. "\x50\x4b\x05\x06\x00\x00\x00\x00"
|
||||||
|
. $count // total # of entries "on this disk"
|
||||||
|
. $count // total # of entries overall
|
||||||
|
. \pack('V', \strlen($ctrldir)) // size of central dir
|
||||||
|
. \pack('V', $this->offset) // offset to start of central dir
|
||||||
|
. "\x00\x00"); // .zip file comment length
|
||||||
|
\fclose($this->out);
|
||||||
|
$this->out = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFile($file, string $name = null) : bool
|
||||||
|
{
|
||||||
|
if (\is_file($file) && $fp = new \SplFileObject($file, 'rb')) {
|
||||||
|
return $this->addFromStream($fp, $name ?: \basename($file));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFromStream($resource, string $name, int $time = 0) : bool
|
||||||
|
{
|
||||||
|
if ($resource instanceof \SplFileObject) {
|
||||||
|
if (!$time) {
|
||||||
|
$time = $resource->getMTime();
|
||||||
|
}
|
||||||
|
} else if (\is_resource($resource)) {
|
||||||
|
$resource = new ZipResource($resource);
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Invalid resource');
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = new ZipEntry(
|
||||||
|
$name,
|
||||||
|
$time,
|
||||||
|
(self::NONE === $this->compression) ? self::NONE : self::DEFLATE
|
||||||
|
);
|
||||||
|
|
||||||
|
$bytes = \fwrite($this->out, $file->getHeader());
|
||||||
|
|
||||||
|
if (self::NONE === $this->compression) {
|
||||||
|
while (!$resource->eof()) {
|
||||||
|
$data = $resource->fread(16384);
|
||||||
|
if (false === $data || '' === $data) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$file->updateCrc32($data);
|
||||||
|
$file->u_len += \strlen($data);
|
||||||
|
$file->c_len += \fwrite($this->out, $data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$zip = \deflate_init(ZLIB_ENCODING_RAW, array('level' => 9));
|
||||||
|
while (!$resource->eof()) {
|
||||||
|
// deflate works best with buffers >32K
|
||||||
|
$data = $resource->fread(65536);
|
||||||
|
if (false === $data || '' === $data) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$file->updateCrc32($data);
|
||||||
|
$file->u_len += \strlen($data);
|
||||||
|
$file->c_len += \fwrite($this->out, \deflate_add($zip, $data, ZLIB_NO_FLUSH));
|
||||||
|
}
|
||||||
|
$file->c_len += \fwrite($this->out, \deflate_add($zip, '', ZLIB_FINISH));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the Data descriptor
|
||||||
|
$bytes += \fwrite($this->out, $file->getDataDescriptor());
|
||||||
|
|
||||||
|
// now add to central directory record
|
||||||
|
$this->ctrl_dir[] = $file->getDirEntry($this->offset);
|
||||||
|
|
||||||
|
$this->offset += $bytes + $file->c_len;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addFromString(string $name, string $data, int $time = 0) : bool
|
||||||
|
{
|
||||||
|
$file = new ZipEntry($name, $time, $this->compression);
|
||||||
|
$file->u_len = \strlen($data);
|
||||||
|
$file->setCrc32(\crc32($data));
|
||||||
|
|
||||||
|
switch ($this->compression)
|
||||||
|
{
|
||||||
|
case self::NONE:
|
||||||
|
break;
|
||||||
|
case self::DEFLATE:
|
||||||
|
$data = \gzdeflate($data, 9);
|
||||||
|
break;
|
||||||
|
case self::BZIP2:
|
||||||
|
$data = \bzcompress($data, 9);
|
||||||
|
break;
|
||||||
|
case self::LZMA:
|
||||||
|
$data = \lzf_compress($data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$file->c_len = \strlen($data);
|
||||||
|
|
||||||
|
$bytes = \fwrite($this->out, $file->getHeader() . $data);
|
||||||
|
|
||||||
|
// now add to central directory record
|
||||||
|
$this->ctrl_dir[] = $file->getDirEntry($this->offset);
|
||||||
|
|
||||||
|
$this->offset += $bytes;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addRecursive($dir, $ignore = '#/(\\.hg(/|$)|\\.hgignore)#') : void
|
||||||
|
{
|
||||||
|
\clearstatcache();
|
||||||
|
$dir = \rtrim($dir,'\\/') . '/';
|
||||||
|
$dirl = \strlen($dir);
|
||||||
|
$iterator = new \RecursiveIteratorIterator(
|
||||||
|
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS /*| \FilesystemIterator::FOLLOW_SYMLINKS*/),
|
||||||
|
\RecursiveIteratorIterator::SELF_FIRST,
|
||||||
|
\RecursiveIteratorIterator::CATCH_GET_CHILD);
|
||||||
|
$ignore_paths = array();
|
||||||
|
foreach ($iterator as $name => $fileinfo) {
|
||||||
|
if ($ignore_paths && \preg_match('#'.\implode('|',$ignore_paths).'#', $name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$ignore || !\preg_match($ignore, $name)) {
|
||||||
|
$this->addFile($name, \substr($name, $dirl));
|
||||||
|
}
|
||||||
|
// like: tar --exclude-caches -czf file.tgz *
|
||||||
|
if (\strpos($name, 'CACHEDIR.TAG')) {
|
||||||
|
$ignore_paths[] = \preg_quote(\dirname($name) . '/','#');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
protected function encrypt($data)
|
||||||
|
{
|
||||||
|
5.1 - File is encrypted using AES encryption
|
||||||
|
6.1 - File is encrypted using non-OAEP key wrapping***
|
||||||
|
6.3 - File is encrypted using Blowfish
|
||||||
|
6.3 - File is encrypted using Twofish
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZipResource /* SplFileObject */
|
||||||
|
{
|
||||||
|
protected $source;
|
||||||
|
function __construct($source) { $this->source = $source; }
|
||||||
|
public function eof() : bool { return \feof($this->source); }
|
||||||
|
public function fread($length) { return \fread($this->source, $length); }
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZipEntry
|
||||||
|
{
|
||||||
|
const
|
||||||
|
NO_CRC32 = "\x00\x00\x00\x00",
|
||||||
|
|
||||||
|
FLAG_ENCRYPTED = 1,
|
||||||
|
// Method 6 - Imploding compression method used
|
||||||
|
FLAG_IMPLODE_4K = 0, // 4K sliding dictionary was used
|
||||||
|
FLAG_IMPLODE_8K = 2, // 8K sliding dictionary was used
|
||||||
|
FLAG_IMPLODE_2SF = 0, // 2 Shannon-Fano trees were used
|
||||||
|
FLAG_IMPLODE_3SF = 4, // 3 Shannon-Fano trees were used
|
||||||
|
// For Methods 8 and 9 - Deflating
|
||||||
|
FLAG_DEFLATE_NORMAL = 0,
|
||||||
|
FLAG_DEFLATE_MAXIMUM = 2,
|
||||||
|
FLAG_DEFLATE_FAST = 4,
|
||||||
|
FLAG_DEFLATE_SUPERFAST = 6,
|
||||||
|
// For Method 14 - LZMA
|
||||||
|
FLAG_LZMA_EOS = 2, // end-of-stream marker is used
|
||||||
|
/* If FLAG_DATA_DESCRIPTOR is set, the fields crc-32, compressed size
|
||||||
|
* and uncompressed size are set to zero in the local header.
|
||||||
|
* The correct values are put in the data descriptor immediately
|
||||||
|
* following the compressed data
|
||||||
|
*/
|
||||||
|
FLAG_DATA_DESCRIPTOR = 8,
|
||||||
|
FLAG_PATCHED = 32, // the file is compressed patched data
|
||||||
|
FLAG_STRONG_ENCRYPTION = 65, // 64 + 1
|
||||||
|
FLAG_EFS_UTF8 = 2048, // Language encoding flag, filename and comment fields for this file MUST be encoded using UTF-8
|
||||||
|
FLAG_ENCRYPTED_CD = 8192; // encrypting the Central Directory
|
||||||
|
|
||||||
|
public
|
||||||
|
/**
|
||||||
|
* uncompressed filesize
|
||||||
|
*/
|
||||||
|
$u_len = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* compressed filesize
|
||||||
|
*/
|
||||||
|
$c_len = 0;
|
||||||
|
|
||||||
|
protected
|
||||||
|
$hctx,
|
||||||
|
$name,
|
||||||
|
$time,
|
||||||
|
$flags = 0,
|
||||||
|
$crc32 = self::NO_CRC32;
|
||||||
|
|
||||||
|
function __construct(string $name, int $time, string $compression)
|
||||||
|
{
|
||||||
|
$this->name = \strtr($name, '\\', '/');
|
||||||
|
|
||||||
|
// Convert Unix time to DOS
|
||||||
|
$time = \getdate($time ?: \time());
|
||||||
|
if ($time['year'] < 1980) {
|
||||||
|
$this->time = "\x00\x00\x21\x00";
|
||||||
|
} else {
|
||||||
|
$time = (($time['year'] - 1980) << 25) | ($time['mon'] << 21) | ($time['mday'] << 16) |
|
||||||
|
($time['hours'] << 11) | ($time['minutes'] << 5) | ($time['seconds'] >> 1);
|
||||||
|
$time = \str_pad(\dechex($time), 8, '0', STR_PAD_LEFT);
|
||||||
|
$this->time = \hex2bin($time[6] . $time[7] . $time[4] . $time[5] . $time[2] . $time[3] . $time[0] . $time[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->compression = $compression;
|
||||||
|
|
||||||
|
$this->flags = self::FLAG_EFS_UTF8;
|
||||||
|
if (Zip::DEFLATE === $compression) {
|
||||||
|
$this->flags |= self::FLAG_DEFLATE_MAXIMUM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getEntry() : string
|
||||||
|
{
|
||||||
|
$versions = array(
|
||||||
|
Zip::NONE => "\x0A\x00", // v1
|
||||||
|
Zip::DEFLATE => "\x14\x00", // v2
|
||||||
|
Zip::BZIP2 => "\x2E\x00", // v4.6
|
||||||
|
Zip::LZMA => "\x3F\x00", // v6.3
|
||||||
|
);
|
||||||
|
return $versions[$this->compression] // ver needed to extract
|
||||||
|
. \pack('v', $this->flags) // gen purpose bit flag
|
||||||
|
. $this->compression // compression method
|
||||||
|
. $this->time // last mod time and date
|
||||||
|
. ($this->crc32 ?: self::NO_CRC32) // crc32
|
||||||
|
. \pack('V', $this->c_len) // compressed filesize
|
||||||
|
. \pack('V', $this->u_len) // uncompressed filesize
|
||||||
|
. \pack('v', \strlen($this->name)) // length of filename
|
||||||
|
. "\x00\x00"; // extra field length
|
||||||
|
// TODO: extra field 4.5 Extensible data fields, the UNIX Extra Field (0x000d)
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCrc32($v) : void
|
||||||
|
{
|
||||||
|
if (\is_int($v)) {
|
||||||
|
$v = \pack('V', $v);
|
||||||
|
}
|
||||||
|
if (\is_string($v) && 4 === \strlen($v)) {
|
||||||
|
$this->crc32 = $v;
|
||||||
|
} else {
|
||||||
|
$this->crc32 = self::NO_CRC32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCrc32(string $data) : void
|
||||||
|
{
|
||||||
|
if (!$this->hctx) {
|
||||||
|
// set up the CRC32 hashing context
|
||||||
|
$this->hctx = \hash_init('crc32b');
|
||||||
|
}
|
||||||
|
\hash_update($this->hctx, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local file header
|
||||||
|
public function getHeader() : string
|
||||||
|
{
|
||||||
|
if (self::NO_CRC32 === $this->crc32) {
|
||||||
|
$this->flags |= self::FLAG_DATA_DESCRIPTOR;
|
||||||
|
}
|
||||||
|
return "\x50\x4b\x03\x04" . $this->getEntry() . $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data descriptor
|
||||||
|
public function getDataDescriptor(bool $signature = false) : string
|
||||||
|
{
|
||||||
|
if ($this->flags & self::FLAG_DATA_DESCRIPTOR) {
|
||||||
|
if ($this->hctx) {
|
||||||
|
// hash_final is a string, not an integer and need to
|
||||||
|
// reverse the hash_final string to little endian
|
||||||
|
$crc = \hash_final($this->hctx, true);
|
||||||
|
$this->setCrc32($crc[3].$crc[2].$crc[1].$crc[0]);
|
||||||
|
$this->hctx = null;
|
||||||
|
}
|
||||||
|
return ($signature ? "\x50\x4b\x07\x08" : '')
|
||||||
|
. $this->crc32
|
||||||
|
. \pack('V', $this->c_len)
|
||||||
|
. \pack('V', $this->u_len);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Central directory file header
|
||||||
|
public function getDirEntry(int $offset) : string
|
||||||
|
{
|
||||||
|
// now add to central directory record
|
||||||
|
return "\x50\x4b\x01\x02"
|
||||||
|
. "\x00\x03" // version made by UNIX
|
||||||
|
. $this->getEntry()
|
||||||
|
. "\x00\x00" // file comment length
|
||||||
|
. "\x00\x00" // disk number start
|
||||||
|
. "\x00\x00" // internal file attributes
|
||||||
|
. "\x20\x00\x00\x00" // external file attributes - 'archive' bit set
|
||||||
|
. \pack('V', $offset) // relative offset of local header
|
||||||
|
. $this->name;
|
||||||
|
// TODO: extra field 4.5 Extensible data fields, the UNIX Extra Field (0x000d)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue