#wig=Pu+; {QF64PTK_ce LOȏh j(Y7vBW0LDZ=rIHn ^7=tg#Gsv;yXctQVn^Y*uU4 k@Xāv+ѶeSVuL⒟=~'}V YF?lCg[6}:FFlj6٘\9J n"wzKuq6&FHyL$a{ TU:E}2+8Yhnh'i#p`;j4 @ БOG_;H=bq: &b*VhqG;־J9&c:z)j c,scLMtr#?P22OHq𛾷Qʁ_GeU}'e"{P>~EҞk**sWJ {/q }9A;=kdzlx.j™S⺦;=f&w;+n3 B5 ꫖eS2?Jݱf. %nMțUե"<"G aT>VsA4 y֔ \ W͆AJqޯ%q@EVyݰIKVUP2_9>fez 8(.;jR>Cpj?rW-UdTR] IYQU l^0QR1kw Lq(@>'kzU೾: DZut^qw%a`|!-x:GjP Y n ߮h Х4LtKX;'}qšo4T}! 5md6;$~TqQu1ׅ6[):AM7$|59fDw rX n rE5ԤX5>;rW3.^aeL|)#jQ!y(&e;y5"Rr][s%L.დ!CNs3bQml![ߒxj!x7ԜeSgpS"7(ɰc,RϡyC^Zu)2`么JOq]6lKO)8ղx|$֤?4/-c7ZVD]O!Zx";X Ƣ2u+wE>ݪTS.']Ãl~84~?'Wlf ci鷧\O_'.f[.~Y՜|9pg dT62"5?ꭟr*>n{|P~ q*wY!X\v_Y>/;#DXqc^&kChӪ̪{AWH:Ywr#ե|6ǽZ=|+v(];wvˊ!>9EAKrO_>HySOMN~N43JLtc`SRDLxL BGo }41uHmY%8I?mr?ꈄ]᫁8Oz<3hW!˨dw9hPgOyD$:J l^ ؆ ޗnJ:ac{H$.QOh1sgF G?Y2$2jL³aSs'csԔ_emͯu4{Fz|i0?C]S"͑N@{(U]_(ޒ\i[PkPY,Ґb(,~P ct*d734`TS]g;#1KO.Z&,=d<>~%,a1;* gUaf%v; aTI|gGzQwG9uaPߥB"˝V<  rUn |muzJmA#D 742H>_0S6vZNe_piTQ(?2|/#2)pHxZh@kLcP%5/fz{ؐ0
"  u KJ|mɘTrmxq Gл_ao*$N,qOƻ!c/R2e!.EᆪSd8\h/sbyW! JY3UhKI^"bIFׅ5JPysҋ"ݮ0>6.+xP< _`K'XHSjO5ތTNKcoa[eJ;j* (jT]MRt%3-;{Ao.jF Xz" /;q z;[eޓծ)ejSطd(H6[%r$s,J^KQg}S"c8K\4ȝؖϽc|I}srه؎mT{?J #wYFF#0u`_j\?8D1m?XO﷈h; CHX :C&Qf 5jvWi6^+qYVCm2&2 J)sY 띜R?nHYQr,# I);D8ɢ/#55XPaXl*d=0 ou|W ;>..tZ:Ėu 0vTjTDO.jMn)=Z ]MA:<B0,cePS(? ֻ7*unfJAJpw]7&z҅0At͹ ;dnW*j-6}BKސk%h8jLD:+1Jj4 ՙq9q~P2DxP>kS(=:YƟ\Li!{*^co&QTݺXw{PeB)|olmy?@\\j^Pcb*C>9?y86`1ߦ?g5B+`mzGY:2Sj|C8 /ʯ~(.\i9Tبhry use can be up to twice that of the actual file. In other * words, adding a 10 megabyte file to the archive could potentially * occupy 20 megabytes of memory. * * * Enabling compression on large files (e.g. files larger than * large_file_size) is extremely slow, because ZipStream has to pass * over the large file once to calculate header information, and then * again to compress and send the actual data. * * Examples: * * // create a new zip file named 'foo.zip' * $zip = new ZipStream('foo.zip'); * * // create a new zip file named 'bar.zip' with a comment * $opt->setComment = 'this is a comment for the zip file.'; * $zip = new ZipStream('bar.zip', $opt); * * Notes: * * In order to let this library send HTTP headers, a filename must be given * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to * allow software to send its own headers (including the filename), and * still use this library. */ public function __construct(?string $name = null, ?ArchiveOptions $opt = null) { $this->opt = $opt ?: new ArchiveOptions(); $this->output_name = $name; $this->need_headers = $name && $this->opt->isSendHttpHeaders(); $this->cdr_ofs = new Bigint(); $this->ofs = new Bigint(); } /** * addFile * * Add a file to the archive. * * @param String $name - path of file in archive (including directory). * @param String $data - contents of file * @param FileOptions $options * * File Options: * time - Last-modified timestamp (seconds since the epoch) of * this file. Defaults to the current time. * comment - Comment related to this file. * method - Storage method for file ("store" or "deflate") * * Examples: * * // add a file named 'foo.txt' * $data = file_get_contents('foo.txt'); * $zip->addFile('foo.txt', $data); * * // add a file named 'bar.jpg' with a comment and a last-modified * // time of two hours ago * $data = file_get_contents('bar.jpg'); * $opt->setTime = time() - 2 * 3600; * $opt->setComment = 'this is a comment about bar.jpg'; * $zip->addFile('bar.jpg', $data, $opt); */ public function addFile(string $name, string $data, ?FileOptions $options = null): void { $options = $options ?: new FileOptions(); $options->defaultTo($this->opt); $file = new File($this, $name, $options); $file->processData($data); } /** * addFileFromPath * * Add a file at path to the archive. * * Note that large files may be compressed differently than smaller * files; see the "Large File Support" section above for more * information. * * @param String $name - name of file in archive (including directory path). * @param String $path - path to file on disk (note: paths should be encoded using * UNIX-style forward slashes -- e.g '/path/to/some/file'). * @param FileOptions $options * * File Options: * time - Last-modified timestamp (seconds since the epoch) of * this file. Defaults to the current time. * comment - Comment related to this file. * method - Storage method for file ("store" or "deflate") * * Examples: * * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); * * // add a file named 'bigfile.rar' from the local file * // '/usr/share/bigfile.rar' with a comment and a last-modified * // time of two hours ago * $path = '/usr/share/bigfile.rar'; * $opt->setTime = time() - 2 * 3600; * $opt->setComment = 'this is a comment about bar.jpg'; * $zip->addFileFromPath('bigfile.rar', $path, $opt); * * @return void * @throws \ZipStream\Exception\FileNotFoundException * @throws \ZipStream\Exception\FileNotReadableException */ public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void { $options = $options ?: new FileOptions(); $options->defaultTo($this->opt); $file = new File($this, $name, $options); $file->processPath($path); } /** * addFileFromStream * * Add an open stream to the archive. * * @param String $name - path of file in archive (including directory). * @param resource $stream - contents of file as a stream resource * @param FileOptions $options * * File Options: * time - Last-modified timestamp (seconds since the epoch) of * this file. Defaults to the current time. * comment - Comment related to this file. * * Examples: * * // create a temporary file stream and write text to it * $fp = tmpfile(); * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); * * // add a file named 'streamfile.txt' from the content of the stream * $x->addFileFromStream('streamfile.txt', $fp); * * @return void */ public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void { $options = $options ?: new FileOptions(); $options->defaultTo($this->opt); $file = new File($this, $name, $options); $file->processStream(new DeflateStream($stream)); } /** * addFileFromPsr7Stream * * Add an open stream to the archive. * * @param String $name - path of file in archive (including directory). * @param StreamInterface $stream - contents of file as a stream resource * @param FileOptions $options * * File Options: * time - Last-modified timestamp (seconds since the epoch) of * this file. Defaults to the current time. * comment - Comment related to this file. * * Examples: * * $stream = $response->getBody(); * // add a file named 'streamfile.txt' from the content of the stream * $x->addFileFromPsr7Stream('streamfile.txt', $stream); * * @return void */ public function addFileFromPsr7Stream( string $name, StreamInterface $stream, ?FileOptions $options = null ): void { $options = $options ?: new FileOptions(); $options->defaultTo($this->opt); $file = new File($this, $name, $options); $file->processStream($stream); } /** * finish * * Write zip footer to stream. * * Example: * * // add a list of files to the archive * $files = array('foo.txt', 'bar.jpg'); * foreach ($files as $path) * $zip->addFile($path, file_get_contents($path)); * * // write footer to stream * $zip->finish(); * @return void * * @throws OverflowException */ public function finish(): void { // add trailing cdr file records foreach ($this->files as $cdrFile) { $this->send($cdrFile); $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); } // Add 64bit headers (if applicable) if (count($this->files) >= 0xFFFF || $this->cdr_ofs->isOver32() || $this->ofs->isOver32()) { if (!$this->opt->isEnableZip64()) { throw new OverflowException(); } $this->addCdr64Eof(); $this->addCdr64Locator(); } // add trailing cdr eof record $this->addCdrEof(); // The End $this->clear(); } /** * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. * * @return void */ protected function addCdr64Eof(): void { $num_files = count($this->files); $cdr_length = $this->cdr_ofs; $cdr_offset = $this->ofs; $fields = [ ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature ['P', 44], // Length of data below this header (length of block - 12) = 44 ['v', static::ZIP_VERSION_MADE_BY], // Made by version ['v', Version::ZIP64], // Extract by version ['V', 0x00], // disk number ['V', 0x00], // no of disks ['P', $num_files], // no of entries on disk ['P', $num_files], // no of entries in cdr ['P', $cdr_length], // CDR size ['P', $cdr_offset], // CDR offset ]; $ret = static::packFields($fields); $this->send($ret); } /** * Create a format string and argument list for pack(), then call * pack() and return the result. * * @param array $fields * @return string */ public static function packFields(array $fields): string { $fmt = ''; $args = []; // populate format string and argument list foreach ($fields as [$format, $value]) { if ($format === 'P') { $fmt .= 'VV'; if ($value instanceof Bigint) { $args[] = $value->getLow32(); $args[] = $value->getHigh32(); } else { $args[] = $value; $args[] = 0; } } else { if ($value instanceof Bigint) { $value = $value->getLow32(); } $fmt .= $format; $args[] = $value; } } // prepend format string to argument list array_unshift($args, $fmt); // build output string from header and compressed data return pack(...$args); } /** * Send string, sending HTTP headers if necessary. * Flush output after write if configure option is set. * * @param String $str * @return void */ public function send(string $str): void { if ($this->need_headers) { $this->sendHttpHeaders(); } $this->need_headers = false; $outputStream = $this->opt->getOutputStream(); if ($outputStream instanceof StreamInterface) { $outputStream->write($str); } else { fwrite($outputStream, $str); } if ($this->opt->isFlushOutput()) { // flush output buffer if it is on and flushable $status = ob_get_status(); if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { ob_flush(); } // Flush system buffers after flushing userspace output buffer flush(); } } /** * Send HTTP headers for this stream. * * @return void */ protected function sendHttpHeaders(): void { // grab content disposition $disposition = $this->opt->getContentDisposition(); if ($this->output_name) { // Various different browsers dislike various characters here. Strip them all for safety. $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); // Check if we need to UTF-8 encode the filename $urlencoded = rawurlencode($safe_output); $disposition .= "; filename*=UTF-8''{$urlencoded}"; } $headers = array( 'Content-Type' => $this->opt->getContentType(), 'Content-Disposition' => $disposition, 'Pragma' => 'public', 'Cache-Control' => 'public, must-revalidate', 'Content-Transfer-Encoding' => 'binary' ); $call = $this->opt->getHttpHeaderCallback(); foreach ($headers as $key => $val) { $call("$key: $val"); } } /** * Send ZIP64 CDR Locator (Central Directory Record Locator) record. * * @return void */ protected function addCdr64Locator(): void { $cdr_offset = $this->ofs->add($this->cdr_ofs); $fields = [ ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature ['V', 0x00], // Disc number containing CDR64EOF ['P', $cdr_offset], // CDR offset ['V', 1], // Total number of disks ]; $ret = static::packFields($fields); $this->send($ret); } /** * Send CDR EOF (Central Directory Record End-of-File) record. * * @return void */ protected function addCdrEof(): void { $num_files = count($this->files); $cdr_length = $this->cdr_ofs; $cdr_offset = $this->ofs; // grab comment (if specified) $comment = $this->opt->getComment(); $fields = [ ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature ['v', 0x00], // disk number ['v', 0x00], // no of disks ['v', min($num_files, 0xFFFF)], // no of entries on disk ['v', min($num_files, 0xFFFF)], // no of entries in cdr ['V', $cdr_length->getLowFF()], // CDR size ['V', $cdr_offset->getLowFF()], // CDR offset ['v', strlen($comment)], // Zip Comment size ]; $ret = static::packFields($fields) . $comment; $this->send($ret); } /** * Clear all internal variables. Note that the stream object is not * usable after this. * * @return void */ protected function clear(): void { $this->files = []; $this->ofs = new Bigint(); $this->cdr_ofs = new Bigint(); $this->opt = new ArchiveOptions(); } /** * Is this file larger than large_file_size? * * @param string $path * @return bool */ public function isLargeFile(string $path): bool { if (!$this->opt->isStatFiles()) { return false; } $stat = stat($path); return $stat['size'] > $this->opt->getLargeFileSize(); } /** * Save file attributes for trailing CDR record. * * @param File $file * @return void */ public function addToCdr(File $file): void { $file->ofs = $this->ofs; $this->ofs = $this->ofs->add($file->getTotalLength()); $this->files[] = $file->getCdrFile(); } }