mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-30 20:49:20 +03:00
Backup using own TAR stream when PharData not available
This commit is contained in:
parent
927c4e6ed1
commit
d1bab197c1
3 changed files with 396 additions and 8 deletions
372
snappymail/v/0.0.0/app/libraries/snappymail/stream/tar.php
Normal file
372
snappymail/v/0.0.0/app/libraries/snappymail/stream/tar.php
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
<?php
|
||||
|
||||
namespace SnappyMail\Stream;
|
||||
|
||||
class TAR
|
||||
{
|
||||
const
|
||||
NONE = "\x00\x00",
|
||||
DEFLATE = "\x08\x00",
|
||||
BZIP2 = "\x0C\x00",
|
||||
LZMA = "\x0E\x00",
|
||||
|
||||
TYPE_FILE = '0',
|
||||
TYPE_LINK = '2',
|
||||
TYPE_DIR = '5';
|
||||
|
||||
protected
|
||||
$gzip = false,
|
||||
$started = false,
|
||||
$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;
|
||||
} else {
|
||||
throw new \Exception("Failed to open output: {$target}");
|
||||
}
|
||||
if (self::DEFLATE === $compression) {
|
||||
$this->gzip = array('w' => null, 'h' => null, 'l' => 0);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
if (false !== $this->gzip) {
|
||||
$name .= '.tgz';
|
||||
$name = \preg_match('#^[\x01-\x7F]*$#D', $name) ? $name : '=?UTF-8?B?'.\base64_encode($name).'?=';
|
||||
\header("Content-Disposition: attachment; filename={$name}");
|
||||
\header("Content-Type: application/x-gzip; name={$name}");
|
||||
} else {
|
||||
$name .= '.tar';
|
||||
$name = \preg_match('#^[\x01-\x7F]*$#D', $name) ? $name : '=?UTF-8?B?'.\base64_encode($name).'?=';
|
||||
\header("Content-Disposition: attachment; filename={$name}");
|
||||
\header("Content-Type: application/x-ustar; name={$name}");
|
||||
}
|
||||
}
|
||||
|
||||
public function close() : void
|
||||
{
|
||||
if ($this->out) {
|
||||
if ($this->started) {
|
||||
// Write tar footer
|
||||
$this->write(pack('a1024', ''));
|
||||
// Stop compression
|
||||
if (!empty($this->gzip['w'])) {
|
||||
\stream_filter_remove($this->gzip['w']);
|
||||
// hash_final is a string, not an integer
|
||||
$crc = \hash_final($this->gzip['h'], 1);
|
||||
// write the little endian CRC32 and uncompressed file size
|
||||
\fwrite($this->out, $crc[3].$crc[2].$crc[1].$crc[0].\pack('V', $this->gzip['l']), 8);
|
||||
}
|
||||
}
|
||||
\fclose($this->out);
|
||||
$this->out = null;
|
||||
}
|
||||
}
|
||||
|
||||
public function addFile($fileinfo, string $name = null) : bool
|
||||
{
|
||||
if (!($fileinfo instanceof \SplFileInfo)) {
|
||||
$fileinfo = new \SplFileInfo($fileinfo);
|
||||
}
|
||||
if (!$name) {
|
||||
$name = $fileinfo->getFilename();
|
||||
}
|
||||
if ($fileinfo->isLink()) {
|
||||
$stat = \lstat($fileinfo);
|
||||
$target = $fileinfo->getLinkTarget();
|
||||
if (\dirname($fileinfo->getPathname()) === \dirname($fileinfo->getRealPath())) {
|
||||
$target = \basename($target);
|
||||
}
|
||||
$this->writeEntryHeader(
|
||||
$name,
|
||||
static::TYPE_LINK,
|
||||
0,
|
||||
$stat['uid'],
|
||||
$stat['gid'],
|
||||
$stat['mode'],
|
||||
$stat['mtime'],
|
||||
$target
|
||||
);
|
||||
} else if ($fileinfo->isDir()) {
|
||||
$this->addDir($name, $fileinfo);
|
||||
} else if ($fileinfo->isFile()) {
|
||||
return $this->addFromStream($fileinfo->openFile('rb'), $name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addFromStream($resource, string $name, int $time = 0) : bool
|
||||
{
|
||||
if (\is_resource($resource)) {
|
||||
$temp = new TarTempResource();
|
||||
while (!\feof($resource)) {
|
||||
$data = $resource->fread(4096);
|
||||
if (false === $data || '' === $data) {
|
||||
break;
|
||||
}
|
||||
$temp->write($data);
|
||||
}
|
||||
$temp->rewind();
|
||||
$resource = $temp;
|
||||
} else if (!$resource instanceof \SplFileObject) {
|
||||
throw new \Exception('Invalid resource');
|
||||
}
|
||||
|
||||
$this->writeEntryHeader(
|
||||
\strtr($name, '\\', '/'),
|
||||
static::TYPE_FILE,
|
||||
$resource->getSize(),
|
||||
$resource->getOwner(),
|
||||
$resource->getGroup(),
|
||||
$resource->getPerms(),
|
||||
$time ?: $resource->getMTime()
|
||||
);
|
||||
$l = 0;
|
||||
while (!$resource->eof()) {
|
||||
// deflate works best with buffers >32K
|
||||
$data = $resource->fread(65536);
|
||||
if (false === $data || '' === $data) {
|
||||
break;
|
||||
}
|
||||
$l += $this->write($data);
|
||||
}
|
||||
if ($l = $l % 512) {
|
||||
$l = 512 - $l;
|
||||
$this->write(\pack("a{$l}", ''));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addFromString(string $name, string $data, int $time = 0) : bool
|
||||
{
|
||||
$this->writeEntryHeader(
|
||||
\strtr($name, '\\', '/'),
|
||||
static::TYPE_FILE,
|
||||
\strlen($data),
|
||||
0,
|
||||
0,
|
||||
420,
|
||||
$time
|
||||
);
|
||||
$l += $this->write($data);
|
||||
if ($l = $l % 512) {
|
||||
$l = 512 - $l;
|
||||
$this->write(\pack("a{$l}", ''));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addDir(string $dirname, \SplFileInfo $fileinfo = null) : void
|
||||
{
|
||||
$this->writeEntryHeader(
|
||||
\rtrim(\strtr($dirname, '\\', '/'), '/') . '/',
|
||||
static::TYPE_DIR,
|
||||
0,
|
||||
$fileinfo ? $fileinfo->getOwner() : 0,
|
||||
$fileinfo ? $fileinfo->getGroup() : 0,
|
||||
$fileinfo ? $fileinfo->getPerms() : 493,
|
||||
$fileinfo ? $fileinfo->getMTime() : 0
|
||||
);
|
||||
}
|
||||
|
||||
public function addRecursive(string $dir, string $target_dir = '', $ignore = '#/(\\.hg(/|$)|\\.hgignore)#') : void
|
||||
{
|
||||
if (!$this->out) {
|
||||
throw new \Exception('Stream closed');
|
||||
}
|
||||
\clearstatcache();
|
||||
$dir = \rtrim($dir,'\\/') . '/';
|
||||
$dirl = \strlen($dir);
|
||||
if ($target_dir) {
|
||||
$target_dir = \rtrim($target_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($fileinfo, $target_dir . \substr($name, $dirl));
|
||||
}
|
||||
// like: tar --exclude-caches -czf file.tgz *
|
||||
if (\strpos($name, 'CACHEDIR.TAG')) {
|
||||
$ignore_paths[] = \preg_quote(\dirname($name) . '/','#');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function start()
|
||||
{
|
||||
if ($this->started) {
|
||||
return;
|
||||
}
|
||||
$this->started = true;
|
||||
if (false !== $this->gzip) {
|
||||
// Write gzip header, see http://www.zlib.org/rfc-gzip.html#member-format
|
||||
if (!\fwrite($this->out, "\x1F\x8B\x08\x00".\pack('V', \time())."\0\x03", 10)) {
|
||||
throw new \Exception('Failed to write to stream');
|
||||
}
|
||||
// Start ZLIB compression (RFC 1950)
|
||||
$this->gzip['w'] = \stream_filter_append($this->out, 'zlib.deflate', STREAM_FILTER_WRITE, array(
|
||||
'level' => 9,
|
||||
// 'window' => 32768,
|
||||
'memory' => 9,
|
||||
));
|
||||
// Start CRC32 hashing
|
||||
$this->gzip['h'] = \hash_init('crc32b');
|
||||
}
|
||||
}
|
||||
|
||||
protected function write($string)/* : int|bool*/
|
||||
{
|
||||
if (!$this->out) {
|
||||
throw new \Exception('Stream closed');
|
||||
}
|
||||
if (!$this->started) {
|
||||
$this->start();
|
||||
}
|
||||
$length = \strlen($string);
|
||||
$written = 0;
|
||||
while ($written < $length) {
|
||||
$bytes = \fwrite($this->out, $written ? \substr($string, $written) : $string);
|
||||
if (!$bytes) {
|
||||
return $written ?: false;
|
||||
}
|
||||
$written += $bytes;
|
||||
}
|
||||
|
||||
if (!empty($this->gzip['h'])) {
|
||||
$this->gzip['l'] += $length;
|
||||
\hash_update($this->gzip['h'], $string);
|
||||
}
|
||||
|
||||
return $written;
|
||||
}
|
||||
|
||||
protected function writeEntryHeader($name, $type, $size, $uid = 0, $gid = 0, $perm = 0, $mtime = 0, $link = '', $prefix = '')
|
||||
{
|
||||
// handle long filename length
|
||||
$paxdata = $paxname = '';
|
||||
if (100 < \strlen($link)) {
|
||||
$length = \strlen($link) + 11;
|
||||
$length += \strlen($length);
|
||||
$paxdata = "{$length} linkpath={$link}\n";
|
||||
$link = '././@LongSymLink';
|
||||
}
|
||||
$l = \strlen($name);
|
||||
if (($paxdata?90:100) < $l) {
|
||||
// split into name and prefix
|
||||
$p = \strpos($name, '/', \max(0, $l - 90));
|
||||
if ($p && $p < $l-1) {
|
||||
$file = \substr($name, $p+1);
|
||||
$prefix = \substr($name, 0, $p);
|
||||
$paxname = \preg_replace('#(^|/)([^/]+)$#', '$1PaxHeader/$2', $file);
|
||||
} else {
|
||||
$file = \basename($name);
|
||||
if (static::TYPE_DIR === $type) {
|
||||
$file .= '/';
|
||||
}
|
||||
$prefix = \dirname($name);
|
||||
$paxname = 'PaxHeader/' . $file;
|
||||
}
|
||||
if (100 < \strlen($file) || 155 < \strlen($prefix)) {
|
||||
// POSIX.1-2001/pax
|
||||
$length = $l + 7;
|
||||
$length += \strlen($length);
|
||||
$paxdata = "{$length} path={$name}\n{$paxdata}";
|
||||
if (static::TYPE_DIR === $type) {
|
||||
$name = \substr($file, 0, 98) . '/';
|
||||
} else {
|
||||
$name = \substr($file, 0, 99);
|
||||
}
|
||||
} else {
|
||||
// POSIX ustar
|
||||
$name = $file;
|
||||
}
|
||||
$paxname = \substr($paxname, 0, 98);
|
||||
}
|
||||
if ($paxdata) {
|
||||
/* Add?
|
||||
$data .= "30 mtime=1461056595.149922432\n"
|
||||
$data .= "30 ctime=1461056595.149922432\n"
|
||||
*/
|
||||
$this->writeUStarEntryHeader(
|
||||
$paxname ?: \preg_replace('#(^|/)([^/]+)$#', '$1PaxHeader/$2', $name),
|
||||
'x',
|
||||
\strlen($paxdata),
|
||||
$uid,
|
||||
$gid,
|
||||
$perm,
|
||||
$mtime,
|
||||
'',
|
||||
$prefix
|
||||
);
|
||||
$l = 512 * \ceil(\strlen($paxdata) / 512);
|
||||
$this->write(\pack("a{$l}", $paxdata));
|
||||
}
|
||||
|
||||
$this->writeUStarEntryHeader($name, $type, $size, $uid, $gid, $perm, $mtime, $link, $prefix);
|
||||
}
|
||||
|
||||
// Writes Pre-POSIX.1-1988 (i.e. v7) and POSIX UStar headers
|
||||
protected function writeUStarEntryHeader($name, $type, $size, $uid = 0, $gid = 0, $perm = 0, $mtime = 0, $link = '', $prefix = '')
|
||||
{
|
||||
$data = \pack('a100a8a8a8a12A12',
|
||||
$name,
|
||||
// values in octal
|
||||
\sprintf("%06u ", \substr(\decoct($perm),-3)),
|
||||
\sprintf("%06u ", \decoct($uid)),
|
||||
\sprintf("%06u ", \decoct($gid)),
|
||||
\sprintf("%011u ", \decoct($size)),
|
||||
\sprintf("%011u", \decoct($mtime ?: \time())));
|
||||
$this->write($data);
|
||||
$checksum = 0;
|
||||
$i = 148;
|
||||
while ($i--) {
|
||||
$checksum += \ord($data[$i]);
|
||||
}
|
||||
|
||||
$data = \pack('a1a100a6a2a32a32a8a8a155a12', $type, $link, 'ustar', '00', '', '', '000000 ', '000000 ', $prefix, '');
|
||||
$checksum += 256;
|
||||
$i = 356;
|
||||
while ($i--) {
|
||||
$checksum += \ord($data[$i]);
|
||||
}
|
||||
|
||||
$this->write(\pack('a8', \sprintf('%06u ', \decoct($checksum))) . $data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TarTempResource extends \SplTempFileObject
|
||||
{
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getOwner()/*: int|false*/ { return 0; }
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getGroup()/*: int|false*/ { return 0; }
|
||||
#[\ReturnTypeWillChange]
|
||||
public function getPerms()/*: int|false*/ { return 420; }
|
||||
}
|
||||
|
|
@ -40,8 +40,7 @@ class ZIP
|
|||
}
|
||||
if (\is_resource($target)) {
|
||||
$this->out = $target;
|
||||
}
|
||||
if (!$this->out) {
|
||||
} else {
|
||||
throw new \Exception("Failed to open output: {$target}");
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +79,7 @@ class ZIP
|
|||
\header('Cache-Control: no-store');
|
||||
\header('Pragma: no-cache');
|
||||
\header('Content-Transfer-Encoding: binary');
|
||||
$name = "{$name}.zip";
|
||||
$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}");
|
||||
|
|
@ -114,6 +113,10 @@ class ZIP
|
|||
|
||||
public function addFromStream($resource, string $name, int $time = 0) : bool
|
||||
{
|
||||
if (!$this->out) {
|
||||
throw new \Exception('Stream closed');
|
||||
}
|
||||
|
||||
if ($resource instanceof \SplFileObject) {
|
||||
if (!$time) {
|
||||
$time = $resource->getMTime();
|
||||
|
|
@ -170,6 +173,10 @@ class ZIP
|
|||
|
||||
public function addFromString(string $name, string $data, int $time = 0) : bool
|
||||
{
|
||||
if (!$this->out) {
|
||||
throw new \Exception('Stream closed');
|
||||
}
|
||||
|
||||
$file = new ZipEntry($name, $time, $this->compression);
|
||||
$file->u_len = \strlen($data);
|
||||
$file->setCrc32(\crc32($data));
|
||||
|
|
@ -202,6 +209,9 @@ class ZIP
|
|||
|
||||
public function addRecursive(string $dir, string $target_dir = '', string $ignore = '#/(\\.hg(/|$)|\\.hgignore)#') : void
|
||||
{
|
||||
if (!$this->out) {
|
||||
throw new \Exception('Stream closed');
|
||||
}
|
||||
\clearstatcache();
|
||||
$dir = \rtrim($dir,'\\/') . '/';
|
||||
$dirl = \strlen($dir);
|
||||
|
|
|
|||
|
|
@ -166,12 +166,15 @@ abstract class Upgrade
|
|||
|
||||
public static function backup() : string
|
||||
{
|
||||
if (!\class_exists('PharData')) {
|
||||
throw new \Exception('PHP Phar is disabled, you must enable it');
|
||||
}
|
||||
// $tar_destination = APP_DATA_FOLDER_PATH . APP_VERSION . '.tar';
|
||||
$tar_destination = APP_DATA_FOLDER_PATH . 'backup-' . \date('YmdHis') . '.tar';
|
||||
$tar = new \PharData($tar_destination);
|
||||
$tar_destination = APP_DATA_FOLDER_PATH . 'backup-' . \date('YmdHis');
|
||||
if (\class_exists('PharData')) {
|
||||
$tar_destination .= '.tar';
|
||||
$tar = new \PharData($tar_destination);
|
||||
} else {
|
||||
$tar_destination .= '.tgz';
|
||||
$tar = new \SnappyMail\Stream\TAR($tar_destination);
|
||||
}
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator(APP_DATA_FOLDER_PATH . '_data_'),
|
||||
\RecursiveIteratorIterator::SELF_FIRST
|
||||
|
|
@ -183,6 +186,9 @@ abstract class Upgrade
|
|||
$tar->addFile($file, \substr($file, $l));
|
||||
}
|
||||
}
|
||||
if ($tar instanceof \SnappyMail\Stream\TAR) {
|
||||
return $tar_destination;
|
||||
}
|
||||
$tar->compress(\Phar::GZ);
|
||||
\unlink($tar_destination);
|
||||
return $tar_destination . '.gz';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue