mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-10-01 09:24:36 +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->open($sZipFileName, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE);
|
||||
$oZip->setArchiveComment('SnappyMail/'.APP_VERSION);
|
||||
|
||||
foreach ($aData as $aItem)
|
||||
{
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
|
||||
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
|
||||
|
||||
if (!empty($sFileHash))
|
||||
{
|
||||
if (!empty($sFileHash)) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||
if (!$oZip->addFile($sFullFileNameHash, $sFileName))
|
||||
{
|
||||
if (!$oZip->addFile($sFullFileNameHash, $sFileName)) {
|
||||
$bError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bError)
|
||||
{
|
||||
if (!$bError) {
|
||||
$bError = !$oZip->close();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$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 {
|
||||
@\unlink($sZipFileName);
|
||||
$oZip = new \PharData($sZipFileName . '.zip', 0, null, \Phar::ZIP);
|
||||
$oZip->compressFiles(\Phar::GZ);
|
||||
|
||||
foreach ($aData as $aItem)
|
||||
{
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileName = (isset($aItem['FileName']) ? (string) $aItem['FileName'] : 'file.dat');
|
||||
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
||||
|
||||
if ($sFileHash)
|
||||
{
|
||||
if ($sFileHash) {
|
||||
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
|
||||
$oZip->addFile($sFullFileNameHash, $sFileName);
|
||||
}
|
||||
}
|
||||
|
||||
$oZip->compressFiles(\Phar::GZ);
|
||||
|
||||
unset($oZip);
|
||||
\rename($sZipFileName . '.zip', $sZipFileName);
|
||||
}
|
||||
|
||||
foreach ($aData as $aItem)
|
||||
{
|
||||
foreach ($aData as $aItem) {
|
||||
$sFileHash = (isset($aItem['FileHash']) ? (string) $aItem['FileHash'] : '');
|
||||
if ($sFileHash)
|
||||
{
|
||||
if ($sFileHash) {
|
||||
$oFilesProvider->Clear($oAccount, $sFileHash);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bError)
|
||||
{
|
||||
if (!$bError) {
|
||||
$mResult = array(
|
||||
'FileHash' => Utils::EncodeKeyValuesQ(array(
|
||||
'V' => APP_VERSION,
|
||||
|
|
|
@ -22,7 +22,7 @@ abstract class Stream
|
|||
if ($i = \ob_get_level()) {
|
||||
# Clear buffers:
|
||||
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/
|
||||
// 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