From 6a82ef2474d709cdc02752be3f85920ef5308d6f Mon Sep 17 00:00:00 2001 From: Matt Porritt Date: Fri, 3 Mar 2023 15:09:08 +1100 Subject: [PATCH] MDL-76923 Libraries: Upgrade ZipStream to 2.4.0 --- lib/thirdpartylibs.xml | 2 +- lib/zipstream/readme_moodle.txt | 6 + lib/zipstream/src/Bigint.php | 39 +-- lib/zipstream/src/DeflateStream.php | 61 +---- lib/zipstream/src/Exception.php | 1 + .../src/Exception/EncodingException.php | 1 + .../src/Exception/FileNotFoundException.php | 1 + .../Exception/FileNotReadableException.php | 1 + .../IncompatibleOptionsException.php | 1 + .../src/Exception/OverflowException.php | 1 + .../Exception/StreamNotReadableException.php | 1 + lib/zipstream/src/File.php | 239 ++++++++---------- lib/zipstream/src/Option/Archive.php | 19 +- lib/zipstream/src/Option/File.php | 5 + lib/zipstream/src/Option/Method.php | 8 +- lib/zipstream/src/Option/Version.php | 11 +- lib/zipstream/src/Stream.php | 72 +++--- lib/zipstream/src/ZipStream.php | 142 ++++++----- 18 files changed, 301 insertions(+), 310 deletions(-) diff --git a/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index b0473bd7fe3..e49bffbd80e 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -584,7 +584,7 @@ All rights reserved. zipstream ZipStream-PHP PHP ZIP Streaming Library - 2.2.0 + 2.4.0 MIT https://github.com/maennchen/ZipStream-PHP diff --git a/lib/zipstream/readme_moodle.txt b/lib/zipstream/readme_moodle.txt index dcdf8166967..efc0eac7f58 100644 --- a/lib/zipstream/readme_moodle.txt +++ b/lib/zipstream/readme_moodle.txt @@ -6,3 +6,9 @@ Instructions to import ZipStream into Moodle: 3/ Ensure any dependencies are also imported (eg psr/http-message and myclabs/php-enum). The dependencies will be listed in the "require" section of the library's composer.json file + +Local changes: + * 2023/03/03 - Version 3.0.0 of the ZipStream library uses "readonly", "enum" and "First-class Callable Syntax" + properties that are only supported by PHP 8.1 and above. + As Moodle 4.2 only requires PHP 8.0 version 2.4.0 (released 9/12/2022) has been used instead. + When Moodle requires PHP 8.1 version 3.0.0 can be used. diff --git a/lib/zipstream/src/Bigint.php b/lib/zipstream/src/Bigint.php index ff84d0d54ab..f2565e98014 100644 --- a/lib/zipstream/src/Bigint.php +++ b/lib/zipstream/src/Bigint.php @@ -1,4 +1,5 @@ fillBytes($value, 0, 8); } - /** - * Fill the bytes field with int - * - * @param int $value - * @param int $start - * @param int $count - * @return void - */ - protected function fillBytes(int $value, int $start, int $count): void - { - for ($i = 0; $i < $count; $i++) { - $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; - $value >>= 8; - } - } - /** * Get an instance * @@ -58,7 +43,7 @@ class Bigint */ public static function fromLowHigh(int $low, int $high): self { - $bigint = new Bigint(); + $bigint = new self(); $bigint->fillBytes($low, 0, 4); $bigint->fillBytes($high, 4, 4); return $bigint; @@ -150,7 +135,7 @@ class Bigint * @param Bigint $other * @return Bigint */ - public function add(Bigint $other): Bigint + public function add(self $other): self { $result = clone $this; $overflow = false; @@ -166,8 +151,24 @@ class Bigint } } if ($overflow) { - throw new OverflowException; + throw new OverflowException(); } return $result; } + + /** + * Fill the bytes field with int + * + * @param int $value + * @param int $start + * @param int $count + * @return void + */ + protected function fillBytes(int $value, int $start, int $count): void + { + for ($i = 0; $i < $count; $i++) { + $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; + $value >>= 8; + } + } } diff --git a/lib/zipstream/src/DeflateStream.php b/lib/zipstream/src/DeflateStream.php index d6c272885b7..210cbd2e3ad 100644 --- a/lib/zipstream/src/DeflateStream.php +++ b/lib/zipstream/src/DeflateStream.php @@ -1,70 +1,27 @@ filter) { - $this->removeDeflateFilter(); - $this->seek(0); - $this->addDeflateFilter($this->options); - } else { - rewind($this->stream); - } + parent::__construct($stream); + trigger_error('Class ' . __CLASS__ . ' is deprecated, delation will be handled internally instead', E_USER_DEPRECATED); } - /** - * Remove the deflate filter - * - * @return void - */ public function removeDeflateFilter(): void { - if (!$this->filter) { - return; - } - stream_filter_remove($this->filter); - $this->filter = null; + trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED); } - /** - * Add a deflate filter - * - * @param Option\File $options - * @return void - */ public function addDeflateFilter(Option\File $options): void { - $this->options = $options; - // parameter 4 for stream_filter_append expects array - // so we convert the option object in an array - $optionsArr = [ - 'comment' => $options->getComment(), - 'method' => $options->getMethod(), - 'deflateLevel' => $options->getDeflateLevel(), - 'time' => $options->getTime() - ]; - $this->filter = stream_filter_append( - $this->stream, - 'zlib.deflate', - STREAM_FILTER_READ, - $optionsArr - ); + trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED); } } diff --git a/lib/zipstream/src/Exception.php b/lib/zipstream/src/Exception.php index 18ccfbbdc1f..03a87672a63 100644 --- a/lib/zipstream/src/Exception.php +++ b/lib/zipstream/src/Exception.php @@ -1,4 +1,5 @@ method = $this->zip->opt->getLargeFileMethod(); - $stream = new DeflateStream(fopen($path, 'rb')); + $stream = new Stream(fopen($path, 'rb')); $this->processStream($stream); $stream->close(); } @@ -162,21 +165,17 @@ class File // Sets Bit 11: Language encoding flag (EFS). If this bit is set, // the filename and comment fields for this file // MUST be encoded using UTF-8. (see APPENDIX D) - if (!mb_check_encoding($name, 'UTF-8') || - !mb_check_encoding($comment, 'UTF-8')) { - throw new EncodingException( - 'File name and comment should use UTF-8 ' . - 'if one of them does not fit into ASCII range.' - ); + if (mb_check_encoding($name, 'UTF-8') && + mb_check_encoding($comment, 'UTF-8')) { + $this->bits |= self::BIT_EFS_UTF8; } - $this->bits |= self::BIT_EFS_UTF8; } if ($this->method->equals(Method::DEFLATE())) { $this->version = Version::DEFLATE(); } - $force = (boolean)($this->bits & self::BIT_ZERO_HEADER) && + $force = (bool)($this->bits & self::BIT_ZERO_HEADER) && $this->zip->opt->isEnableZip64(); $footer = $this->buildZip64ExtraBlock($force); @@ -228,6 +227,97 @@ class File return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); } + /** + * Create and send data descriptor footer for this file. + * + * @return void + */ + public function addFileFooter(): void + { + if ($this->bits & self::BIT_ZERO_HEADER) { + // compressed and uncompressed size + $sizeFormat = 'V'; + if ($this->zip->opt->isEnableZip64()) { + $sizeFormat = 'P'; + } + $fields = [ + ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], + ['V', $this->crc], // CRC32 + [$sizeFormat, $this->zlen], // Length of compressed data + [$sizeFormat, $this->len], // Length of original data + ]; + + $footer = ZipStream::packFields($fields); + $this->zip->send($footer); + } else { + $footer = ''; + } + $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); + $this->zip->addToCdr($this); + } + + public function processStream(StreamInterface $stream): void + { + $this->zlen = new Bigint(); + $this->len = new Bigint(); + + if ($this->zip->opt->isZeroHeader()) { + $this->processStreamWithZeroHeader($stream); + } else { + $this->processStreamWithComputedHeader($stream); + } + } + + /** + * Send CDR record for specified file. + * + * @return string + */ + public function getCdrFile(): string + { + $name = static::filterFilename($this->name); + + // get attributes + $comment = $this->opt->getComment(); + + // get dos timestamp + $time = static::dosTime($this->opt->getTime()->getTimestamp()); + + $footer = $this->buildZip64ExtraBlock(); + + $fields = [ + ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature + ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version + ['v', $this->version->getValue()], // Extract by version + ['v', $this->bits], // General purpose bit flags - data descriptor flag set + ['v', $this->method->getValue()], // Compression method + ['V', $time], // Timestamp (DOS Format) + ['V', $this->crc], // CRC32 + ['V', $this->zlen->getLowFF()], // Compressed Data Length + ['V', $this->len->getLowFF()], // Original Data Length + ['v', strlen($name)], // Length of filename + ['v', strlen($footer)], // Extra data len (see above) + ['v', strlen($comment)], // Length of comment + ['v', 0], // Disk number + ['v', 0], // Internal File Attributes + ['V', 32], // External File Attributes + ['V', $this->ofs->getLowFF()], // Relative offset of local header + ]; + + // pack fields, then append name and comment + $header = ZipStream::packFields($fields); + + return $header . $name . $footer . $comment; + } + + /** + * @return Bigint + */ + public function getTotalLength(): Bigint + { + return $this->totalLength; + } + /** * Convert a UNIX timestamp to a DOS timestamp. * @@ -241,14 +331,14 @@ class File // set lower-bound on dates if ($d['year'] < 1980) { - $d = array( + $d = [ 'year' => 1980, 'mon' => 1, 'mday' => 1, 'hours' => 0, 'minutes' => 0, - 'seconds' => 0 - ); + 'seconds' => 0, + ]; } // remove extra years from 1980 @@ -266,7 +356,6 @@ class File protected function buildZip64ExtraBlock(bool $force = false): string { - $fields = []; if ($this->len->isOver32($force)) { $fields[] = ['P', $this->len]; // Length of original data @@ -303,49 +392,6 @@ class File return ZipStream::packFields($fields); } - /** - * Create and send data descriptor footer for this file. - * - * @return void - */ - - public function addFileFooter(): void - { - - if ($this->bits & self::BIT_ZERO_HEADER) { - // compressed and uncompressed size - $sizeFormat = 'V'; - if ($this->zip->opt->isEnableZip64()) { - $sizeFormat = 'P'; - } - $fields = [ - ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], - ['V', $this->crc], // CRC32 - [$sizeFormat, $this->zlen], // Length of compressed data - [$sizeFormat, $this->len], // Length of original data - ]; - - $footer = ZipStream::packFields($fields); - $this->zip->send($footer); - } else { - $footer = ''; - } - $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); - $this->zip->addToCdr($this); - } - - public function processStream(StreamInterface $stream): void - { - $this->zlen = new Bigint(); - $this->len = new Bigint(); - - if ($this->zip->opt->isZeroHeader()) { - $this->processStreamWithZeroHeader($stream); - } else { - $this->processStreamWithComputedHeader($stream); - } - } - protected function processStreamWithZeroHeader(StreamInterface $stream): void { $this->bits |= self::BIT_ZERO_HEADER; @@ -363,7 +409,7 @@ class File $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); $total += strlen($data); if ($size > 0 && $total > $size) { - $data = substr($data, 0 , strlen($data)-($total - $size)); + $data = substr($data, 0, strlen($data)-($total - $size)); } $this->deflateData($stream, $data, $options); if ($options & self::SEND) { @@ -417,71 +463,8 @@ class File $this->readStream($stream, self::COMPUTE); $stream->rewind(); - // incremental compression with deflate_add - // makes this second read unnecessary - // but it is only available from PHP 7.0 - if (!$this->deflate && $stream instanceof DeflateStream && $this->method->equals(Method::DEFLATE())) { - $stream->addDeflateFilter($this->opt); - $this->zlen = new Bigint(); - while (!$stream->eof()) { - $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); - $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); - } - $stream->rewind(); - } - $this->addFileHeader(); $this->readStream($stream, self::SEND); $this->addFileFooter(); } - - /** - * Send CDR record for specified file. - * - * @return string - */ - public function getCdrFile(): string - { - $name = static::filterFilename($this->name); - - // get attributes - $comment = $this->opt->getComment(); - - // get dos timestamp - $time = static::dosTime($this->opt->getTime()->getTimestamp()); - - $footer = $this->buildZip64ExtraBlock(); - - $fields = [ - ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature - ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version - ['v', $this->version->getValue()], // Extract by version - ['v', $this->bits], // General purpose bit flags - data descriptor flag set - ['v', $this->method->getValue()], // Compression method - ['V', $time], // Timestamp (DOS Format) - ['V', $this->crc], // CRC32 - ['V', $this->zlen->getLowFF()], // Compressed Data Length - ['V', $this->len->getLowFF()], // Original Data Length - ['v', strlen($name)], // Length of filename - ['v', strlen($footer)], // Extra data len (see above) - ['v', strlen($comment)], // Length of comment - ['v', 0], // Disk number - ['v', 0], // Internal File Attributes - ['V', 32], // External File Attributes - ['V', $this->ofs->getLowFF()] // Relative offset of local header - ]; - - // pack fields, then append name and comment - $header = ZipStream::packFields($fields); - - return $header . $name . $footer . $comment; - } - - /** - * @return Bigint - */ - public function getTotalLength(): Bigint - { - return $this->totalLength; - } } diff --git a/lib/zipstream/src/Option/Archive.php b/lib/zipstream/src/Option/Archive.php index 21d14d8c53c..374dd1debb7 100644 --- a/lib/zipstream/src/Option/Archive.php +++ b/lib/zipstream/src/Option/Archive.php @@ -1,4 +1,5 @@ 4 GB or file count > 64k) @@ -54,6 +61,7 @@ final class Archive * @var bool */ private $enableZip64 = true; + /** * Enable streaming files with single read where * general purpose bit 3 indicates local file header @@ -64,6 +72,7 @@ final class Archive * @var bool */ private $zeroHeader = false; + /** * Enable reading file stat for determining file size. * When a 32-bit system reads file size that is @@ -77,11 +86,13 @@ final class Archive * @var bool */ private $statFiles = true; + /** * Enable flush after every write to output stream. * @var bool */ private $flushOutput = false; + /** * HTTP Content-Disposition. Defaults to * 'attachment', where @@ -93,6 +104,7 @@ final class Archive * @var string */ private $contentDisposition = 'attachment'; + /** * Note that this does nothing if you are * not sending HTTP headers. @@ -100,6 +112,7 @@ final class Archive * @var string */ private $contentType = 'application/x-zip'; + /** * @var int */ @@ -159,12 +172,12 @@ final class Archive $this->sendHttpHeaders = $sendHttpHeaders; } - public function getHttpHeaderCallback(): Callable + public function getHttpHeaderCallback(): callable { return $this->httpHeaderCallback; } - public function setHttpHeaderCallback(Callable $httpHeaderCallback): void + public function setHttpHeaderCallback(callable $httpHeaderCallback): void { $this->httpHeaderCallback = $httpHeaderCallback; } diff --git a/lib/zipstream/src/Option/File.php b/lib/zipstream/src/Option/File.php index 05ff7b5ee52..37e37ced608 100644 --- a/lib/zipstream/src/Option/File.php +++ b/lib/zipstream/src/Option/File.php @@ -1,4 +1,5 @@ */ class Method extends Enum { - const STORE = 0x00; - const DEFLATE = 0x08; + public const STORE = 0x00; + + public const DEFLATE = 0x08; } diff --git a/lib/zipstream/src/Option/Version.php b/lib/zipstream/src/Option/Version.php index ba2144246e0..f3daa852d00 100644 --- a/lib/zipstream/src/Option/Version.php +++ b/lib/zipstream/src/Option/Version.php @@ -1,4 +1,5 @@ */ class Version extends Enum { - const STORE = 0x000A; // 1.00 - const DEFLATE = 0x0014; // 2.00 - const ZIP64 = 0x002D; // 4.50 + public const STORE = 0x000A; // 1.00 + + public const DEFLATE = 0x0014; // 2.00 + + public const ZIP64 = 0x002D; // 4.50 } diff --git a/lib/zipstream/src/Stream.php b/lib/zipstream/src/Stream.php index 686c3a6b08f..d80e70f6a5f 100644 --- a/lib/zipstream/src/Stream.php +++ b/lib/zipstream/src/Stream.php @@ -1,8 +1,11 @@ stream = $stream; } + /** + * Reads all data from the stream into a string, from the beginning to end. + * + * This method MUST attempt to seek to the beginning of the stream before + * reading data and read the stream until the end is reached. + * + * Warning: This could attempt to load a large amount of data into memory. + * + * This method MUST NOT raise an exception in order to conform with PHP's + * string casting operations. + * + * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring + * @return string + */ + public function __toString(): string + { + try { + $this->seek(0); + } catch (RuntimeException $e) { + } + return (string) stream_get_contents($this->stream); + } + /** * Closes the stream and any underlying resources. * @@ -49,28 +75,6 @@ class Stream implements StreamInterface return $result; } - /** - * Reads all data from the stream into a string, from the beginning to end. - * - * This method MUST attempt to seek to the beginning of the stream before - * reading data and read the stream until the end is reached. - * - * Warning: This could attempt to load a large amount of data into memory. - * - * This method MUST NOT raise an exception in order to conform with PHP's - * string casting operations. - * - * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * @return string - */ - public function __toString(): string - { - try { - $this->seek(0); - } catch (RuntimeException $e) {} - return (string) stream_get_contents($this->stream); - } - /** * Seek to a position in the stream. * @@ -86,10 +90,10 @@ class Stream implements StreamInterface public function seek($offset, $whence = SEEK_SET): void { if (!$this->isSeekable()) { - throw new RuntimeException; + throw new RuntimeException(); } if (fseek($this->stream, $offset, $whence) !== 0) { - throw new RuntimeException; + throw new RuntimeException(); } } @@ -142,7 +146,7 @@ class Stream implements StreamInterface { $position = ftell($this->stream); if ($position === false) { - throw new RuntimeException; + throw new RuntimeException(); } return $position; } @@ -182,12 +186,12 @@ class Stream implements StreamInterface public function write($string): int { if (!$this->isWritable()) { - throw new RuntimeException; + throw new RuntimeException(); } if (fwrite($this->stream, $string) === false) { - throw new RuntimeException; + throw new RuntimeException(); } - return \mb_strlen($string); + return mb_strlen($string); } /** @@ -212,16 +216,16 @@ class Stream implements StreamInterface * call returns fewer bytes. * @return string Returns the data read from the stream, or an empty string * if no bytes are available. - * @throws \RuntimeException if an error occurs. + * @throws RuntimeException if an error occurs. */ public function read($length): string { if (!$this->isReadable()) { - throw new RuntimeException; + throw new RuntimeException(); } $result = fread($this->stream, $length); if ($result === false) { - throw new RuntimeException; + throw new RuntimeException(); } return $result; } @@ -244,17 +248,17 @@ class Stream implements StreamInterface * Returns the remaining contents in a string * * @return string - * @throws \RuntimeException if unable to read or an error occurs while + * @throws RuntimeException if unable to read or an error occurs while * reading. */ public function getContents(): string { if (!$this->isReadable()) { - throw new RuntimeException; + throw new RuntimeException(); } $result = stream_get_contents($this->stream); if ($result === false) { - throw new RuntimeException; + throw new RuntimeException(); } return $result; } diff --git a/lib/zipstream/src/ZipStream.php b/lib/zipstream/src/ZipStream.php index d281d259fa0..c01da7233c4 100644 --- a/lib/zipstream/src/ZipStream.php +++ b/lib/zipstream/src/ZipStream.php @@ -1,4 +1,5 @@ defaultTo($this->opt); $file = new File($this, $name, $options); - $file->processStream(new DeflateStream($stream)); + $file->processStream(new Stream($stream)); } /** @@ -375,34 +381,6 @@ class ZipStream $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. @@ -476,6 +454,62 @@ class ZipStream } } + /** + * 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(); + } + + /** + * 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); + } + /** * Send HTTP headers for this stream. * @@ -495,13 +529,13 @@ class ZipStream $disposition .= "; filename*=UTF-8''{$urlencoded}"; } - $headers = array( + $headers = [ 'Content-Type' => $this->opt->getContentType(), 'Content-Disposition' => $disposition, 'Pragma' => 'public', 'Cache-Control' => 'public, must-revalidate', - 'Content-Transfer-Encoding' => 'binary' - ); + 'Content-Transfer-Encoding' => 'binary', + ]; $call = $this->opt->getHttpHeaderCallback(); foreach ($headers as $key => $val) { @@ -571,32 +605,4 @@ class ZipStream $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(); - } }