ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
ZipStream.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types=1);
3 
4 namespace ZipStream;
5 
9 use ZipStream\Option\File as FileOptions;
11 
58 class ZipStream
59 {
83  const ZIP_VERSION_MADE_BY = 0x603;
84 
90  const FILE_HEADER_SIGNATURE = 0x04034b50;
91  const CDR_FILE_SIGNATURE = 0x02014b50;
92  const CDR_EOF_SIGNATURE = 0x06054b50;
93  const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50;
94  const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50;
95  const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50;
96 
102  public $opt;
103 
107  public $files = [];
108 
112  public $cdr_ofs;
113 
117  public $ofs;
118 
122  protected $need_headers;
123 
127  protected $output_name;
128 
171  public function __construct(?string $name = null, ?ArchiveOptions $opt = null)
172  {
173  $this->opt = $opt ?: new ArchiveOptions();
174 
175  $this->output_name = $name;
176  $this->need_headers = $name && $this->opt->isSendHttpHeaders();
177 
178  $this->cdr_ofs = new Bigint();
179  $this->ofs = new Bigint();
180  }
181 
210  public function addFile(string $name, string $data, ?FileOptions $options = null): void
211  {
212  $options = $options ?: new FileOptions();
213  $options->defaultTo($this->opt);
214 
215  $file = new File($this, $name, $options);
216  $file->processData($data);
217  }
218 
256  public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void
257  {
258  $options = $options ?: new FileOptions();
259  $options->defaultTo($this->opt);
260 
261  $file = new File($this, $name, $options);
262  $file->processPath($path);
263  }
264 
290  public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void
291  {
292  $options = $options ?: new FileOptions();
293  $options->defaultTo($this->opt);
294 
295  $file = new File($this, $name, $options);
296  $file->processStream(new DeflateStream($stream));
297  }
298 
324  public function addFileFromPsr7Stream(
325  string $name,
327  ?FileOptions $options = null
328  ): void {
329  $options = $options ?: new FileOptions();
330  $options->defaultTo($this->opt);
331 
332  $file = new File($this, $name, $options);
333  $file->processStream($stream);
334  }
335 
354  public function finish(): void
355  {
356  // add trailing cdr file records
357  foreach ($this->files as $cdrFile) {
358  $this->send($cdrFile);
359  $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile)));
360  }
361 
362  // Add 64bit headers (if applicable)
363  if (count($this->files) >= 0xFFFF ||
364  $this->cdr_ofs->isOver32() ||
365  $this->ofs->isOver32()) {
366  if (!$this->opt->isEnableZip64()) {
367  throw new OverflowException();
368  }
369 
370  $this->addCdr64Eof();
371  $this->addCdr64Locator();
372  }
373 
374  // add trailing cdr eof record
375  $this->addCdrEof();
376 
377  // The End
378  $this->clear();
379  }
380 
386  protected function addCdr64Eof(): void
387  {
388  $num_files = count($this->files);
389  $cdr_length = $this->cdr_ofs;
390  $cdr_offset = $this->ofs;
391 
392  $fields = [
393  ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature
394  ['P', 44], // Length of data below this header (length of block - 12) = 44
395  ['v', static::ZIP_VERSION_MADE_BY], // Made by version
396  ['v', Version::ZIP64], // Extract by version
397  ['V', 0x00], // disk number
398  ['V', 0x00], // no of disks
399  ['P', $num_files], // no of entries on disk
400  ['P', $num_files], // no of entries in cdr
401  ['P', $cdr_length], // CDR size
402  ['P', $cdr_offset], // CDR offset
403  ];
404 
405  $ret = static::packFields($fields);
406  $this->send($ret);
407  }
408 
416  public static function packFields(array $fields): string
417  {
418  $fmt = '';
419  $args = [];
420 
421  // populate format string and argument list
422  foreach ($fields as [$format, $value]) {
423  if ($format === 'P') {
424  $fmt .= 'VV';
425  if ($value instanceof Bigint) {
426  $args[] = $value->getLow32();
427  $args[] = $value->getHigh32();
428  } else {
429  $args[] = $value;
430  $args[] = 0;
431  }
432  } else {
433  if ($value instanceof Bigint) {
434  $value = $value->getLow32();
435  }
436  $fmt .= $format;
437  $args[] = $value;
438  }
439  }
440 
441  // prepend format string to argument list
442  array_unshift($args, $fmt);
443 
444  // build output string from header and compressed data
445  return pack(...$args);
446  }
447 
455  public function send(string $str): void
456  {
457  if ($this->need_headers) {
458  $this->sendHttpHeaders();
459  }
460  $this->need_headers = false;
461 
462  fwrite($this->opt->getOutputStream(), $str);
463 
464  if ($this->opt->isFlushOutput()) {
465  // flush output buffer if it is on and flushable
466  $status = ob_get_status();
467  if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) {
468  ob_flush();
469  }
470 
471  // Flush system buffers after flushing userspace output buffer
472  flush();
473  }
474  }
475 
481  protected function sendHttpHeaders(): void
482  {
483  // grab content disposition
484  $disposition = $this->opt->getContentDisposition();
485 
486  if ($this->output_name) {
487  // Various different browsers dislike various characters here. Strip them all for safety.
488  $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name));
489 
490  // Check if we need to UTF-8 encode the filename
491  $urlencoded = rawurlencode($safe_output);
492  $disposition .= "; filename*=UTF-8''{$urlencoded}";
493  }
494 
495  $headers = array(
496  'Content-Type' => $this->opt->getContentType(),
497  'Content-Disposition' => $disposition,
498  'Pragma' => 'public',
499  'Cache-Control' => 'public, must-revalidate',
500  'Content-Transfer-Encoding' => 'binary'
501  );
502 
503  $call = $this->opt->getHttpHeaderCallback();
504  foreach ($headers as $key => $val) {
505  $call("$key: $val");
506  }
507  }
508 
514  protected function addCdr64Locator(): void
515  {
516  $cdr_offset = $this->ofs->add($this->cdr_ofs);
517 
518  $fields = [
519  ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature
520  ['V', 0x00], // Disc number containing CDR64EOF
521  ['P', $cdr_offset], // CDR offset
522  ['V', 1], // Total number of disks
523  ];
524 
525  $ret = static::packFields($fields);
526  $this->send($ret);
527  }
528 
534  protected function addCdrEof(): void
535  {
536  $num_files = count($this->files);
537  $cdr_length = $this->cdr_ofs;
538  $cdr_offset = $this->ofs;
539 
540  // grab comment (if specified)
541  $comment = $this->opt->getComment();
542 
543  $fields = [
544  ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature
545  ['v', 0x00], // disk number
546  ['v', 0x00], // no of disks
547  ['v', min($num_files, 0xFFFF)], // no of entries on disk
548  ['v', min($num_files, 0xFFFF)], // no of entries in cdr
549  ['V', $cdr_length->getLowFF()], // CDR size
550  ['V', $cdr_offset->getLowFF()], // CDR offset
551  ['v', strlen($comment)], // Zip Comment size
552  ];
553 
554  $ret = static::packFields($fields) . $comment;
555  $this->send($ret);
556  }
557 
564  protected function clear(): void
565  {
566  $this->files = [];
567  $this->ofs = new Bigint();
568  $this->cdr_ofs = new Bigint();
569  $this->opt = new ArchiveOptions();
570  }
571 
578  public function isLargeFile(string $path): bool
579  {
580  if (!$this->opt->isStatFiles()) {
581  return false;
582  }
583  $stat = stat($path);
584  return $stat['size'] > $this->opt->getLargeFileSize();
585  }
586 
593  public function addToCdr(File $file): void
594  {
595  $file->ofs = $this->ofs;
596  $this->ofs = $this->ofs->add($file->getTotalLength());
597  $this->files[] = $file->getCdrFile();
598  }
599 }
Class Version .
Definition: Bigint.php:4
$path
Definition: aliased.php:25
$format
Definition: metadata.php:141
$files
Definition: metarefresh.php:49
static init(int $value=0)
Get an instance.
Definition: Bigint.php:47
addFile(string $name, string $data, ?FileOptions $options=null)
addFile
Definition: ZipStream.php:210
getTotalLength()
Definition: File.php:473
send(string $str)
Send string, sending HTTP headers if necessary.
Definition: ZipStream.php:455
addFileFromPsr7Stream(string $name, StreamInterface $stream, ?FileOptions $options=null)
addFileFromPsr7Stream
Definition: ZipStream.php:324
addCdrEof()
Send CDR EOF (Central Directory Record End-of-File) record.
Definition: ZipStream.php:534
$stream
PHP stream implementation.
addFileFromStream(string $name, $stream, ?FileOptions $options=null)
addFileFromStream
Definition: ZipStream.php:290
__construct(?string $name=null, ?ArchiveOptions $opt=null)
Create a new ZipStream object.
Definition: ZipStream.php:171
addToCdr(File $file)
Save file attributes for trailing CDR record.
Definition: ZipStream.php:593
isLargeFile(string $path)
Is this file larger than large_file_size?
Definition: ZipStream.php:578
getCdrFile()
Send CDR record for specified file.
Definition: File.php:433
$comment
Definition: buildRTE.php:83
clear()
Clear all internal variables.
Definition: ZipStream.php:564
addFileFromPath(string $name, string $path, ?FileOptions $options=null)
addFileFromPath
Definition: ZipStream.php:256
$ret
Definition: parser.php:6
static packFields(array $fields)
Create a format string and argument list for pack(), then call pack() and return the result...
Definition: ZipStream.php:416
$key
Definition: croninfo.php:18
addCdr64Locator()
Send ZIP64 CDR Locator (Central Directory Record Locator) record.
Definition: ZipStream.php:514
addCdr64Eof()
Send ZIP64 CDR EOF (Central Directory Record End-of-File) record.
Definition: ZipStream.php:386
Describes a data stream.
$data
Definition: bench.php:6
sendHttpHeaders()
Send HTTP headers for this stream.
Definition: ZipStream.php:481