1
0
mirror of https://github.com/Ne-Lexa/php-zip.git synced 2025-10-21 10:07:25 +02:00

Completely rewritten code.

Implement read-only zip file - class \PhpZip\ZipFile, create and update zip file - \PhpZip\ZipOutputFile.
Supports ZIP64 ext, Traditional PKWARE Encryption, WinZip AES Encryption.
This commit is contained in:
Ne-Lexa
2016-09-26 12:04:38 +03:00
parent 5c23e588ff
commit 560a94c910
48 changed files with 7785 additions and 2905 deletions

View File

@@ -0,0 +1,32 @@
<?php
namespace PhpZip\Util;
use PhpZip\Exception\ZipException;
/**
* Crypto Utils
*/
class CryptoUtil
{
/**
* Returns random bytes.
*
* @param int $length
* @return string
* @throws ZipException
*/
public static final function randomBytes($length)
{
$length = (int)$length;
if (function_exists('random_bytes')) {
return random_bytes($length);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($length);
} elseif (function_exists('mcrypt_create_iv')) {
return mcrypt_create_iv($length);
} else {
throw new ZipException('Extension openssl or mcrypt not loaded');
}
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace PhpZip\Util;
use PhpZip\Exception\ZipException;
/**
* Convert unix timestamp values to DOS date/time values and vice versa.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class DateTimeConverter
{
/**
* Smallest supported DOS date/time value in a ZIP file,
* which is January 1st, 1980 AD 00:00:00 local time.
*/
const MIN_DOS_TIME = 0x210000; // (1 << 21) | (1 << 16)
/**
* Largest supported DOS date/time value in a ZIP file,
* which is December 31st, 2107 AD 23:59:58 local time.
*/
const MAX_DOS_TIME = 0xff9fbf7d; // ((2107 - 1980) << 25) | (12 << 21) | (31 << 16) | (23 << 11) | (59 << 5) | (58 >> 1);
/**
* Convert a 32 bit integer DOS date/time value to a UNIX timestamp value.
*
* @param int $dosTime Dos date/time
* @return int Unix timestamp
*/
public static function toUnixTimestamp($dosTime)
{
if (self::MIN_DOS_TIME > $dosTime) {
$dosTime = self::MIN_DOS_TIME;
} elseif (self::MAX_DOS_TIME < $dosTime) {
$dosTime = self::MAX_DOS_TIME;
}
return mktime(
($dosTime >> 11) & 0x1f, // hour
($dosTime >> 5) & 0x3f, // minute
2 * ($dosTime & 0x1f), // second
($dosTime >> 21) & 0x0f, // month
($dosTime >> 16) & 0x1f, // day
1980 + (($dosTime >> 25) & 0x7f) // year
);
}
/**
* Converts a UNIX timestamp value to a DOS date/time value.
*
* @param int $unixTimestamp The number of seconds since midnight, January 1st,
* 1970 AD UTC.
* @return int A DOS date/time value reflecting the local time zone and
* rounded down to even seconds
* and is in between DateTimeConverter::MIN_DOS_TIME and DateTimeConverter::MAX_DOS_TIME.
* @throws ZipException If unix timestamp is negative.
*/
public static function toDosTime($unixTimestamp)
{
if (0 > $unixTimestamp) {
throw new ZipException("Negative unix timestamp: " . $unixTimestamp);
}
$date = getdate($unixTimestamp);
if ($date['year'] < 1980) {
return self::MIN_DOS_TIME;
}
$date['year'] -= 1980;
return ($date['year'] << 25 | $date['mon'] << 21 |
$date['mday'] << 16 | $date['hours'] << 11 |
$date['minutes'] << 5 | $date['seconds'] >> 1);
}
}

View File

@@ -0,0 +1,222 @@
<?php
namespace PhpZip\Util;
use PhpZip\Util\Iterator\IgnoreFilesFilterIterator;
use PhpZip\Util\Iterator\IgnoreFilesRecursiveFilterIterator;
/**
* Files util.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class FilesUtil
{
/**
* Is empty directory
*
* @param string $dir Directory
* @return bool
*/
public static function isEmptyDir($dir)
{
if (!is_readable($dir)) {
return false;
}
return count(scandir($dir)) === 2;
}
/**
* Remove recursive directory.
*
* @param string $dir Directory path.
*/
public static function removeDir($dir)
{
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileInfo) {
$function = ($fileInfo->isDir() ? 'rmdir' : 'unlink');
$function($fileInfo->getRealPath());
}
rmdir($dir);
}
/**
* Convert glob pattern to regex pattern.
*
* @param string $globPattern
* @return string
*/
public static function convertGlobToRegEx($globPattern)
{
// Remove beginning and ending * globs because they're useless
$globPattern = trim($globPattern, '*');
$escaping = false;
$inCurrent = 0;
$chars = str_split($globPattern);
$regexPattern = '';
foreach ($chars AS $currentChar) {
switch ($currentChar) {
case '*':
$regexPattern .= ($escaping ? "\\*" : '.*');
$escaping = false;
break;
case '?':
$regexPattern .= ($escaping ? "\\?" : '.');
$escaping = false;
break;
case '.':
case '(':
case ')':
case '+':
case '|':
case '^':
case '$':
case '@':
case '%':
$regexPattern .= '\\' . $currentChar;
$escaping = false;
break;
case '\\':
if ($escaping) {
$regexPattern .= "\\\\";
$escaping = false;
} else {
$escaping = true;
}
break;
case '{':
if ($escaping) {
$regexPattern .= "\\{";
} else {
$regexPattern = '(';
$inCurrent++;
}
$escaping = false;
break;
case '}':
if ($inCurrent > 0 && !$escaping) {
$regexPattern .= ')';
$inCurrent--;
} else if ($escaping)
$regexPattern = "\\}";
else
$regexPattern = "}";
$escaping = false;
break;
case ',':
if ($inCurrent > 0 && !$escaping) {
$regexPattern .= '|';
} else if ($escaping)
$regexPattern .= "\\,";
else
$regexPattern = ",";
break;
default:
$escaping = false;
$regexPattern .= $currentChar;
}
}
return $regexPattern;
}
/**
* Search files.
*
* @param string $inputDir
* @param bool $recursive
* @param array $ignoreFiles
* @return array Searched file list
*/
public static function fileSearchWithIgnore($inputDir, $recursive = true, array $ignoreFiles = [])
{
$directoryIterator = $recursive ?
new \RecursiveDirectoryIterator($inputDir) :
new \DirectoryIterator($inputDir);
if (!empty($ignoreFiles)) {
$directoryIterator = $recursive ?
new IgnoreFilesRecursiveFilterIterator($directoryIterator, $ignoreFiles) :
new IgnoreFilesFilterIterator($directoryIterator, $ignoreFiles);
}
$iterator = $recursive ?
new \RecursiveIteratorIterator($directoryIterator) :
new \IteratorIterator($directoryIterator);
$fileList = [];
foreach ($iterator as $file) {
if ($file instanceof \SplFileInfo) {
$fileList[] = $file->getPathname();
}
}
return $fileList;
}
/**
* Search files from glob pattern.
*
* @param string $globPattern
* @param int $flags
* @param bool $recursive
* @return array Searched file list
*/
public static function globFileSearch($globPattern, $flags = 0, $recursive = true)
{
$flags = (int)$flags;
$recursive = (bool)$recursive;
$files = glob($globPattern, $flags);
if (!$recursive) {
return $files;
}
foreach (glob(dirname($globPattern) . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $dir) {
$files = array_merge($files, self::globFileSearch($dir . '/' . basename($globPattern), $flags, $recursive));
}
return $files;
}
/**
* Search files from regex pattern.
*
* @param string $folder
* @param string $pattern
* @param bool $recursive
* @return array Searched file list
*/
public static function regexFileSearch($folder, $pattern, $recursive = true)
{
$directoryIterator = $recursive ? new \RecursiveDirectoryIterator($folder) : new \DirectoryIterator($folder);
$iterator = $recursive ? new \RecursiveIteratorIterator($directoryIterator) : new \IteratorIterator($directoryIterator);
$regexIterator = new \RegexIterator($iterator, $pattern, \RegexIterator::MATCH);
$fileList = [];
foreach ($regexIterator as $file) {
if ($file instanceof \SplFileInfo) {
$fileList[] = $file->getPathname();
}
}
return $fileList;
}
/**
* Convert bytes to human size.
*
* @param int $size Size bytes
* @param string|null $unit Unit support 'GB', 'MB', 'KB'
* @return string
*/
public static function humanSize($size, $unit = null)
{
if (($unit === null && $size >= 1 << 30) || $unit === "GB")
return number_format($size / (1 << 30), 2) . "GB";
if (($unit === null && $size >= 1 << 20) || $unit === "MB")
return number_format($size / (1 << 20), 2) . "MB";
if (($unit === null && $size >= 1 << 10) || $unit === "KB")
return number_format($size / (1 << 10), 2) . "KB";
return number_format($size) . " bytes";
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace PhpZip\Util\Iterator;
use PhpZip\Util\StringUtil;
/**
* Iterator for ignore files.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class IgnoreFilesFilterIterator extends \FilterIterator
{
/**
* Ignore list files
*
* @var array
*/
private $ignoreFiles = ['..'];
/**
* @param \Iterator $iterator
* @param array $ignoreFiles
*/
public function __construct(\Iterator $iterator, array $ignoreFiles)
{
parent::__construct($iterator);
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
}
/**
* Check whether the current element of the iterator is acceptable
* @link http://php.net/manual/en/filteriterator.accept.php
* @return bool true if the current element is acceptable, otherwise false.
* @since 5.1.0
*/
public function accept()
{
/**
* @var \SplFileInfo $fileInfo
*/
$fileInfo = $this->current();
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
foreach ($this->ignoreFiles as $ignoreFile) {
// handler dir and sub dir
if ($fileInfo->isDir()
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
) {
return false;
}
// handler filename
if (StringUtil::endsWith($pathname, $ignoreFile)) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace PhpZip\Util\Iterator;
use PhpZip\Util\StringUtil;
/**
* Recursive iterator for ignore files.
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class IgnoreFilesRecursiveFilterIterator extends \RecursiveFilterIterator
{
/**
* Ignore list files
*
* @var array
*/
private $ignoreFiles = ['..'];
/**
* @param \RecursiveIterator $iterator
* @param array $ignoreFiles
*/
public function __construct(\RecursiveIterator $iterator, array $ignoreFiles)
{
parent::__construct($iterator);
$this->ignoreFiles = array_merge($this->ignoreFiles, $ignoreFiles);
}
/**
* Check whether the current element of the iterator is acceptable
* @link http://php.net/manual/en/filteriterator.accept.php
* @return bool true if the current element is acceptable, otherwise false.
* @since 5.1.0
*/
public function accept()
{
/**
* @var \SplFileInfo $fileInfo
*/
$fileInfo = $this->current();
$pathname = str_replace('\\', '/', $fileInfo->getPathname());
foreach ($this->ignoreFiles as $ignoreFile) {
// handler dir and sub dir
if ($fileInfo->isDir()
&& $ignoreFile[strlen($ignoreFile) - 1] === '/'
&& StringUtil::endsWith($pathname, substr($ignoreFile, 0, -1))
) {
return false;
}
// handler filename
if (StringUtil::endsWith($pathname, $ignoreFile)) {
return false;
}
}
return true;
}
/**
* @return IgnoreFilesRecursiveFilterIterator
*/
public function getChildren()
{
return new self($this->getInnerIterator()->getChildren(), $this->ignoreFiles);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace PhpZip\Util;
use PhpZip\Exception\ZipException;
/**
* Pack util
*
* @author Ne-Lexa alexey@nelexa.ru
* @license MIT
*/
class PackUtil
{
/**
* @param int|string $longValue
* @return string
*/
public static function packLongLE($longValue)
{
// TODO test if (version_compare(PHP_VERSION, '5.6.3') >= 0) {return pack("P", $longValue);}
$left = 0xffffffff00000000;
$right = 0x00000000ffffffff;
$r = ($longValue & $left) >> 32;
$l = $longValue & $right;
return pack('VV', $l, $r);
}
/**
* @param string|int $value
* @return int
* @throws ZipException
*/
public static function unpackLongLE($value)
{
// TODO test if (version_compare(PHP_VERSION, '5.6.3') >= 0){ return current(unpack('P', $value)); }
$unpack = unpack('Va/Vb', $value);
return $unpack['a'] + ($unpack['b'] << 32);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace PhpZip\Util;
/**
* String Util
*/
class StringUtil
{
/**
* @param string $haystack
* @param string $needle
* @return bool
*/
public static function startsWith($haystack, $needle)
{
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
}
/**
* @param string $haystack
* @param string $needle
* @return bool
*/
public static function endsWith($haystack, $needle)
{
return $needle === "" || (($temp = strlen($haystack) - strlen($needle)) >= 0
&& strpos($haystack, $needle, $temp) !== false);
}
}