%PDF- %PDF-
Direktori : /www/varak.net/nextcloud.varak.net/3rdparty/deepdiver1975/tarstreamer/src/ |
Current File : //www/varak.net/nextcloud.varak.net/3rdparty/deepdiver1975/tarstreamer/src/TarStreamer.php |
<?php namespace ownCloud\TarStreamer; use ownCloud\TarStreamer\TarHeader; class TarStreamer { public const REGTYPE = 0; public const DIRTYPE = 5; public const XHDTYPE = 'x'; public const LONGNAMETYPE = 'L'; /** * Process in 1 MB chunks */ protected $blockSize = 1048576; protected $outStream; protected $needHeaders = false; protected $longNameHeaderType; /** * Create a new TarStreamer object. * * @param array $options */ public function __construct($options = []) { if (isset($options['outstream'])) { $this->outStream = $options['outstream']; } else { $this->outStream = fopen('php://output', 'w'); // turn off output buffering while (ob_get_level() > 0) { ob_end_flush(); } } if (isset($options['longnames'])) { $this->longNameHeaderType = $options['longnames']; } else { $this->longNameHeaderType = self::LONGNAMETYPE; } } /** * Send appropriate http headers before streaming the tar file and disable output buffering. * This method, if used, has to be called before adding anything to the tar file. * * @param string $archiveName Filename of archive to be created (optional, default 'archive.tar') * @param string $contentType Content mime type to be set (optional, default 'application/x-tar') * @throws \Exception */ public function sendHeaders($archiveName = 'archive.tar', $contentType = 'application/x-tar') { $encodedArchiveName = rawurlencode($archiveName); if (headers_sent($headerFile, $headerLine)) { throw new \Exception("Unable to send file $encodedArchiveName. HTML Headers have already been sent from $headerFile in line $headerLine"); } $buffer = ob_get_contents(); if (!empty($buffer)) { throw new \Exception("Unable to send file $encodedArchiveName. Output buffer already contains text (typically warnings or errors)."); } $headers = [ 'Pragma' => 'public', 'Last-Modified' => gmdate('D, d M Y H:i:s T'), 'Expires' => '0', 'Accept-Ranges' => 'bytes', 'Connection' => 'Keep-Alive', 'Content-Type' => $contentType, 'Cache-Control' => 'public, must-revalidate', 'Content-Transfer-Encoding' => 'binary', ]; // Use UTF-8 filenames when not using Internet Explorer if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE') > 0) { header('Content-Disposition: attachment; filename="' . rawurlencode($archiveName) . '"'); } else { header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($archiveName) . '; filename="' . rawurlencode($archiveName) . '"'); } foreach ($headers as $key => $value) { header("$key: $value"); } } /** * Add a file to the archive at the specified location and file name. * * @param resource $stream Stream to read data from * @param string $filePath Filepath and name to be used in the archive. * @param int $size * @param array $options Optional, additional options * Valid options are: * * int timestamp: timestamp for the file (default: current time) * @return bool $success */ public function addFileFromStream($stream, $filePath, $size, $options = []) { if (!\is_resource($stream) || get_resource_type($stream) != 'stream') { return false; } $this->initFileStreamTransfer($filePath, self::REGTYPE, $size, $options); // send file blocks while ($data = fread($stream, $this->blockSize)) { // send data $this->streamFilePart($data); } // complete the file stream $this->completeFileStream($size); return true; } /** * Explicitly adds a directory to the tar (necessary for empty directories) * * @param string $name Name (path) of the directory * @param array $opt Additional options to set * Valid options are: * * int timestamp: timestamp for the file (default: current time) * @return void */ public function addEmptyDir($name, $opt = []) { $opt['type'] = self::DIRTYPE; // send header $this->initFileStreamTransfer($name, self::DIRTYPE, 0, $opt); // complete the file stream $this->completeFileStream(0); } /** * Close the archive. * A closed archive can no longer have new files added to it. After * closing, the file is completely written to the output stream. * @return bool $success */ public function finalize() { // tar requires the end of the file have two 512 byte null blocks $this->send(pack('a1024', '')); // flush the data to the output fflush($this->outStream); return true; } /** * Initialize a file stream * * @param string $name file path or just name * @param int|string $type type of the item * @param int $size size in bytes of the file * @param array $opt array (optional) * Valid options are: * * int timestamp: timestamp for the file (default: current time) */ protected function initFileStreamTransfer($name, $type, $size, $opt = []) { $dirName = (\dirname($name) == '.') ? '' : \dirname($name); $fileName = ($type == self::DIRTYPE) ? basename($name) . '/' : basename($name); // handle long file names if (\strlen($fileName) > 99 || \strlen($dirName) > 154) { $this->writeLongName($fileName, $dirName); } // process optional arguments $time = isset($opt['timestamp']) ? $opt['timestamp'] : time(); $tarHeader = new TarHeader(); $header = $tarHeader->setName($fileName) ->setSize($size) ->setMtime($time) ->setTypeflag($type) ->setPrefix($dirName) ->getHeader() ; // print header $this->send($header); } protected function writeLongName($fileName, $dirName) { $internalPath = trim($dirName . '/' . $fileName, '/'); if ($this->longNameHeaderType === self::XHDTYPE) { // Long names via PAX $pax = $this->paxGenerate([ 'path' => $internalPath]); $paxSize = \strlen($pax); $this->initFileStreamTransfer('', self::XHDTYPE, $paxSize); $this->streamFilePart($pax); $this->completeFileStream($paxSize); } else { // long names via 'L' header $pathSize = \strlen($internalPath); $tarHeader = new TarHeader(); $header = $tarHeader->setName('././@LongLink') ->setSize($pathSize) ->setTypeflag(self::LONGNAMETYPE) ->getHeader() ; $this->send($header); $this->streamFilePart($internalPath); $this->completeFileStream($pathSize); } } /** * Stream the next part of the current file stream. * * @param string $data raw data to send */ protected function streamFilePart($data) { // send data $this->send($data); // flush the data to the output fflush($this->outStream); } /** * Complete the current file stream * @param $size */ protected function completeFileStream($size) { // ensure we pad the last block so that it is 512 bytes if (($mod = ($size % 512)) > 0) { $this->send(pack('a' . (512 - $mod), '')); } // flush the data to the output fflush($this->outStream); } /** * Send string, sending HTTP headers if necessary. * * @param string $data data to send */ protected function send($data) { if ($this->needHeaders) { $this->sendHeaders(); } $this->needHeaders = false; fwrite($this->outStream, $data); } /** * Generate a PAX string * * @param array $fields key value mapping * @return string PAX formatted string * @link http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current tar / PAX spec */ protected function paxGenerate($fields) { $lines = ''; foreach ($fields as $name => $value) { // build the line and the size $line = ' ' . $name . '=' . $value . "\n"; $size = \strlen((string) \strlen($line)) + \strlen($line); // add the line $lines .= $size . $line; } return $lines; } }