. * * Inspired by * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) * and * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) * * Unix-File attributes according to * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute * * @author Nicolai Ehemann * @author André Rothe * @copyright Copyright (C) 2013-2015 Nicolai Ehemann and contributors * @license GNU GPL * @version 1.0 */ namespace ZipStreamer; class ZipStreamer { const VERSION = "1.0"; const ZIP_LOCAL_FILE_HEADER = 0x04034b50; // local file header signature const ZIP_DATA_DESCRIPTOR_HEADER = 0x08074b50; //data descriptor header signature const ZIP_CENTRAL_FILE_HEADER = 0x02014b50; // central file header signature const ZIP_END_OF_CENTRAL_DIRECTORY = 0x06054b50; // end of central directory record const ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; //zip64 end of central directory record const ZIP64_END_OF_CENTRAL_DIR_LOCATOR = 0x07064b50; // zip64 end of central directory locator const ATTR_MADE_BY_VERSION = 0x032d; // made by version (upper byte: UNIX, lower byte v4.5) const STREAM_CHUNK_SIZE = 1048560; // 16 * 65535 = almost 1mb chunks, for best deflate performance private $extFileAttrFile; private $extFileAttrDir; /** @var stream output stream zip file is written to */ private $outStream; /** @var boolean zip64 enabled */ private $zip64 = True; /** @var int compression method */ private $compress; /** @var int compression level */ private $level; /** @var array central directory record */ private $cdRec = array(); /** @var int offset of next file to be added */ private $offset; /** @var boolean indicates zip is finalized and sent to client; no further addition possible */ private $isFinalized = false; /** @var bool only used for unit testing */ public $turnOffOutputBuffering = true; /** * Constructor. Initializes ZipStreamer object for immediate usage. * @param array $options Optional, ZipStreamer and zip file options as key/value pairs. * Valid options are: * * outstream: stream the zip file is output to (default: stdout) * * zip64: enabled/disable zip64 support (default: True) * * compress: int, compression method (one of COMPR::STORE, * COMPR::DEFLATE, default COMPR::STORE) * can be overridden for single files * * level: int, compression level (one of COMPR::NORMAL, * COMPR::MAXIMUM, COMPR::SUPERFAST, default COMPR::NORMAL) */ function __construct($options = NULL) { $defaultOptions = array( 'outstream' => NULL, 'zip64' => True, 'compress' => COMPR::STORE, 'level' => COMPR::NORMAL, ); if (is_null($options)) { $options = array(); } $options = array_merge($defaultOptions, $options); if ($options['outstream']) { $this->outstream = $options['outstream']; } else { $this->outstream = fopen('php://output', 'w'); } $this->zip64 = $options['zip64']; $this->compress = $options['compress']; $this->level = $options['level']; $this->validateCompressionOptions($this->compress, $this->level); //TODO: is this advisable/necessary? if (ini_get('zlib.output_compression')) { ini_set('zlib.output_compression', 'Off'); } // initialize default external file attributes $this->extFileAttrFile = UNIX::getExtFileAttr(UNIX::S_IFREG | UNIX::S_IRUSR | UNIX::S_IWUSR | UNIX::S_IRGRP | UNIX::S_IROTH); $this->extFileAttrDir = UNIX::getExtFileAttr(UNIX::S_IFDIR | UNIX::S_IRWXU | UNIX::S_IRGRP | UNIX::S_IXGRP | UNIX::S_IROTH | UNIX::S_IXOTH) | DOS::getExtFileAttr(DOS::DIR); $this->offset = Count64::construct(0, !$this->zip64); } function __destruct() { $this->isFinalized = true; $this->cdRec = null; } private function getVersionToExtract($isDir) { if ($this->zip64) { $version = 0x2d; // 4.5 - File uses ZIP64 format extensions } else if ($isDir) { $version = 0x14; // 2.0 - File is a folder (directory) } else { $version = 0x0a; // 1.0 - Default value } return $version; } /** * Send appropriate http headers before streaming the zip file and disable output buffering. * This method, if used, has to be called before adding anything to the zip file. * * @param string $archiveName Filename of archive to be created (optional, default 'archive.zip') * @param string $contentType Content mime type to be set (optional, default 'application/zip') */ public function sendHeaders($archiveName = 'archive.zip', $contentType = 'application/zip') { $headerFile = null; $headerLine = null; if (!headers_sent($headerFile, $headerLine) or die("

Error: Unable to send file " . "$archiveName. HTML Headers have already been sent from " . "$headerFile in line $headerLine" . "

")) { if ((ob_get_contents() === false || ob_get_contents() == '') or die("\n

Error: Unable to send file " . "$archiveName.epub. Output buffer " . "already contains text (typically warnings or errors).

")) { header('Pragma: public'); header('Last-Modified: ' . gmdate('D, d M Y H:i:s T')); header('Expires: 0'); header('Accept-Ranges: bytes'); header('Connection: Keep-Alive'); header('Content-Type: ' . $contentType); // 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) . '"' ); } header('Content-Transfer-Encoding: binary'); } } $this->flush(); // turn off output buffering if ($this->turnOffOutputBuffering) { @ob_end_flush(); } } /** * Add a file to the archive at the specified location and file name. * * @param string $stream Stream to read data from * @param string $filePath Filepath and name to be used in the archive. * @param array $options Optional, additional options * Valid options are: * * int timestamp: timestamp for the file (default: current time) * * string comment: comment to be added for this file (default: none) * * int compress: compression method (override global option for this file) * * int level: compression level (override global option for this file) * @return bool $success */ public function addFileFromStream($stream, $filePath, $options = NULL) { if ($this->isFinalized) { return false; } $defaultOptions = array( 'timestamp' => NULL, 'comment' => NULL, 'compress' => $this->compress, 'level' => $this->level, ); if (is_null($options)) { $options = array(); } $options = array_merge($defaultOptions, $options); $this->validateCompressionOptions($options['compress'], $options['level']); if (!is_resource($stream) || get_resource_type($stream) != 'stream') { return false; } $filePath = self::normalizeFilePath($filePath); $gpFlags = GPFLAGS::ADD; list($gpFlags, $lfhLength) = $this->beginFile($filePath, False, $options['comment'], $options['timestamp'], $gpFlags, $options['compress']); list($dataLength, $gzLength, $dataCRC32) = $this->streamFileData($stream, $options['compress'], $options['level']); $ddLength = $this->addDataDescriptor($dataLength, $gzLength, $dataCRC32); // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $options['timestamp'], $gpFlags, $options['compress'], $dataLength, $gzLength, $dataCRC32, $this->extFileAttrFile, False); // calc offset $this->offset->add($ddLength)->add($lfhLength)->add($gzLength); return true; } /** * Add an empty directory entry to the zip archive. * * @param string $directoryPath Directory Path and name to be added to the archive. * @param array $options Optional, additional options * Valid options are: * * int timestamp: timestamp for the file (default: current time) * * string comment: comment to be added for this file (default: none) * @return bool $success */ public function addEmptyDir($directoryPath, $options = NULL) { if ($this->isFinalized) { return false; } $defaultOptions = array( 'timestamp' => NULL, 'comment' => NULL, ); if (is_null($options)) { $options = array(); } $options = array_merge($defaultOptions, $options); $directoryPath = self::normalizeFilePath($directoryPath) . '/'; if (strlen($directoryPath) > 0) { $gpFlags = 0x0000; $gzMethod = COMPR::STORE; // Compression type 0 = stored list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, True, $options['comment'], $options['timestamp'], $gpFlags, $gzMethod); // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($directoryPath, $options['timestamp'], $gpFlags, $gzMethod, Count64::construct(0, !$this->zip64), Count64::construct(0, !$this->zip64), 0, $this->extFileAttrDir, True); // calc offset $this->offset->add($lfhLength); return true; } return false; } /** * Close the archive. * A closed archive can no longer have new files added to it. After * closing, the zip file is completely written to the output stream. * @return bool $success */ public function finalize() { if (!$this->isFinalized) { // print central directory $cd = implode('', $this->cdRec); $this->write($cd); if ($this->zip64) { // print the zip64 end of central directory record $this->write($this->buildZip64EndOfCentralDirectoryRecord(strlen($cd))); // print the zip64 end of central directory locator $this->write($this->buildZip64EndOfCentralDirectoryLocator(strlen($cd))); } // print end of central directory record $this->write($this->buildEndOfCentralDirectoryRecord(strlen($cd))); $this->flush(); $this->isFinalized = true; $cd = null; $this->cdRec = null; return true; } return false; } private function validateCompressionOptions($compress, $level) { if (COMPR::STORE === $compress) { } else if (COMPR::DEFLATE === $compress) { if (COMPR::NONE !== $level && !class_exists(DeflatePeclStream::PECL1_DEFLATE_STREAM_CLASS) && !class_exists(DeflatePeclStream::PECL2_DEFLATE_STREAM_CLASS)) { throw new \Exception('unable to use compression method DEFLATE with level other than NONE (requires pecl_http >= 0.10)'); } } else { throw new \Exception('invalid option ' . $compress . ' (compression method)'); } if (!(COMPR::NONE === $level || COMPR::NORMAL === $level || COMPR::MAXIMUM === $level || COMPR::SUPERFAST === $level)) { throw new \Exception('invalid option ' . $level . ' (compression level'); } } private function write($data) { return fwrite($this->outstream, $data); } private function flush() { return fflush($this->outstream); } private function beginFile($filePath, $isDir, $fileComment, $timestamp, $gpFlags, $gzMethod, $dataLength = 0, $gzLength = 0, $dataCRC32 = 0) { $isFileUTF8 = mb_check_encoding($filePath, 'UTF-8') && !mb_check_encoding($filePath, 'ASCII'); $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, 'UTF-8') && !mb_check_encoding($fileComment, 'ASCII'); if ($isFileUTF8 || $isCommentUTF8) { $gpFlags |= GPFLAGS::EFS; } $localFileHeader = $this->buildLocalFileHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $isDir, $dataCRC32); $this->write($localFileHeader); return array($gpFlags, strlen($localFileHeader)); } private function streamFileData($stream, $compress, $level) { $dataLength = Count64::construct(0, !$this->zip64); $gzLength = Count64::construct(0, !$this->zip64); $hashCtx = hash_init('crc32b'); if (COMPR::DEFLATE === $compress) { $compStream = DeflateStream::create($level); } while (!feof($stream) && $data = fread($stream, self::STREAM_CHUNK_SIZE)) { $dataLength->add(strlen($data)); hash_update($hashCtx, $data); if (COMPR::DEFLATE === $compress) { $data = $compStream->update($data); } $gzLength->add(strlen($data)); $this->write($data); $this->flush(); } if (COMPR::DEFLATE === $compress) { $data = $compStream->finish(); $gzLength->add(strlen($data)); $this->write($data); $this->flush(); } $crc = unpack('N', hash_final($hashCtx, true)); return array($dataLength, $gzLength, $crc[1]); } private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) { return '' . pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) . pack16le(28) // size of this "extra" block 2 bytes . pack64le($dataLength) // original uncompressed file size 8 bytes . pack64le($gzLength) // size of compressed data 8 bytes . pack64le($this->offset) // offset of local header record 8 bytes . pack32le(0); // number of the disk on which this file starts 4 bytes } private function buildLocalFileHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $isDir = False, $dataCRC32 = 0) { $versionToExtract = $this->getVersionToExtract($isDir); $dosTime = self::getDosTime($timestamp); if ($this->zip64) { $zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength); $dataLength = -1; $gzLength = -1; } else { $zip64Ext = ''; } return '' . pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50) . pack16le($versionToExtract) // version needed to extract 2 bytes . pack16le($gpFlags) // general purpose bit flag 2 bytes . pack16le($gzMethod) // compression method 2 bytes . pack32le($dosTime) // last mod file time 2 bytes // last mod file date 2 bytes . pack32le($dataCRC32) // crc-32 4 bytes . pack32le($gzLength) // compressed size 4 bytes . pack32le($dataLength) // uncompressed size 4 bytes . pack16le(strlen($filePath)) // file name length 2 bytes . pack16le(strlen($zip64Ext)) // extra field length 2 bytes . $filePath // file name (variable size) . $zip64Ext; // extra field (variable size) } private function addDataDescriptor($dataLength, $gzLength, $dataCRC32) { if ($this->zip64) { $length = 24; $packedGzLength = pack64le($gzLength); $packedDataLength = pack64le($dataLength); } else { $length = 16; $packedGzLength = pack32le($gzLength->getLoBytes()); $packedDataLength = pack32le($dataLength->getLoBytes()); } $this->write('' . pack32le(self::ZIP_DATA_DESCRIPTOR_HEADER) // data descriptor header signature 4 bytes (0x08074b50) . pack32le($dataCRC32) // crc-32 4 bytes . $packedGzLength // compressed size 4/8 bytes (depending on zip64 enabled) . $packedDataLength // uncompressed size 4/8 bytes (depending on zip64 enabled) .''); return $length; } private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { $versionToExtract = $this->getVersionToExtract(False); $cdRecCount = sizeof($this->cdRec); return '' . pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50) . pack64le(44) // size of zip64 end of central directory // record 8 bytes . pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes . pack16le($versionToExtract) // version needed to extract 2 bytes . pack32le(0) // number of this disk 4 bytes . pack32le(0) // number of the disk with the start of the // central directory 4 bytes . pack64le($cdRecCount) // total number of entries in the central // directory on this disk 8 bytes . pack64le($cdRecCount) // total number of entries in the // central directory 8 bytes . pack64le($cdRecLength) // size of the central directory 8 bytes . pack64le($this->offset) // offset of start of central directory // with respect to the starting disk number 8 bytes . ''; // zip64 extensible data sector (variable size) } private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { $zip64RecStart = Count64::construct($this->offset, !$this->zip64)->add($cdRecLength); return '' . pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) . pack32le(0) // number of the disk with the start of the // zip64 end of central directory 4 bytes . pack64le($zip64RecStart) // relative offset of the zip64 end of // central directory record 8 bytes . pack32le(1); // total number of disks 4 bytes } private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $dataCRC32, $extFileAttr, $isDir) { $versionToExtract = $this->getVersionToExtract($isDir); $dosTime = self::getDosTime($timestamp); if ($this->zip64) { $zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength); $dataLength = -1; $gzLength = -1; $diskNo = -1; $offset = -1; } else { $zip64Ext = ''; $dataLength = $dataLength->getLoBytes(); $gzLength = $gzLength->getLoBytes(); $diskNo = 0; $offset = $this->offset->getLoBytes(); } return '' . pack32le(self::ZIP_CENTRAL_FILE_HEADER) //central file header signature 4 bytes (0x02014b50) . pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes . pack16le($versionToExtract) // version needed to extract 2 bytes . pack16le($gpFlags) //general purpose bit flag 2 bytes . pack16le($gzMethod) //compression method 2 bytes . pack32le($dosTime) //last mod file time 2 bytes //last mod file date 2 bytes . pack32le($dataCRC32) //crc-32 4 bytes . pack32le($gzLength) //compressed size 4 bytes . pack32le($dataLength) //uncompressed size 4 bytes . pack16le(strlen($filePath)) //file name length 2 bytes . pack16le(strlen($zip64Ext)) //extra field length 2 bytes . pack16le(0) //file comment length 2 bytes . pack16le($diskNo) //disk number start 2 bytes . pack16le(0) //internal file attributes 2 bytes . pack32le($extFileAttr) //external file attributes 4 bytes . pack32le($offset) //relative offset of local header 4 bytes . $filePath //file name (variable size) . $zip64Ext //extra field (variable size) //TODO: implement? . ''; //file comment (variable size) } private function buildEndOfCentralDirectoryRecord($cdRecLength) { if ($this->zip64) { $diskNumber = -1; $cdRecCount = min(sizeof($this->cdRec), 0xffff); $cdRecLength = -1; $offset = -1; } else { $diskNumber = 0; $cdRecCount = sizeof($this->cdRec); $offset = $this->offset->getLoBytes(); } //throw new \Exception(sprintf("zip64 %d diskno %d", $this->zip64, $diskNumber)); return '' . pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50) . pack16le($diskNumber) // number of this disk 2 bytes . pack16le($diskNumber) // number of the disk with the // start of the central directory 2 bytes . pack16le($cdRecCount) // total number of entries in the // central directory on this disk 2 bytes . pack16le($cdRecCount) // total number of entries in the // central directory 2 bytes . pack32le($cdRecLength) // size of the central directory 4 bytes . pack32le($offset) // offset of start of central // directory with respect to the // starting disk number 4 bytes . pack16le(0) // .ZIP file comment length 2 bytes //TODO: implement? . ''; // .ZIP file comment (variable size) } // Utility methods //////////////////////////////////////////////////////// private static function normalizeFilePath($filePath) { return trim(str_replace('\\', '/', $filePath), '/'); } /** * Calculate the 2 byte dostime used in the zip entries. * * @param int $timestamp * @return 2-byte encoded DOS Date */ public static function getDosTime($timestamp = 0) { $timestamp = (int) $timestamp; $oldTZ = @date_default_timezone_get(); date_default_timezone_set('UTC'); $date = ($timestamp == 0 ? getdate() : getdate($timestamp)); date_default_timezone_set($oldTZ); if ($date['year'] >= 1980) { return (($date['mday'] + ($date['mon'] << 5) + (($date['year'] - 1980) << 9)) << 16) | (($date['seconds'] >> 1) + ($date['minutes'] << 5) + ($date['hours'] << 11)); } return 0x0000; } } abstract class ExtFileAttr { /* ZIP external file attributes layout TTTTsstrwxrwxrwx0000000000ADVSHR ^^^^____________________________ UNIX file type ^^^_________________________ UNIX setuid, setgid, sticky ^^^^^^^^^________________ UNIX permissions ^^^^^^^^________ "lower-middle byte" (TODO: what is this?) ^^^^^^^^ DOS attributes (reserved, reserved, archived, directory, volume, system, hidden, read-only */ public static function getExtFileAttr($attr) { return $attr; } } class UNIX extends ExtFileAttr { // Octal const S_IFIFO = 0010000; /* named pipe (fifo) */ const S_IFCHR = 0020000; /* character special */ const S_IFDIR = 0040000; /* directory */ const S_IFBLK = 0060000; /* block special */ const S_IFREG = 0100000; /* regular */ const S_IFLNK = 0120000; /* symbolic link */ const S_IFSOCK = 0140000; /* socket */ const S_ISUID = 0004000; /* set user id on execution */ const S_ISGID = 0002000; /* set group id on execution */ const S_ISTXT = 0001000; /* sticky bit */ const S_IRWXU = 0000700; /* RWX mask for owner */ const S_IRUSR = 0000400; /* R for owner */ const S_IWUSR = 0000200; /* W for owner */ const S_IXUSR = 0000100; /* X for owner */ const S_IRWXG = 0000070; /* RWX mask for group */ const S_IRGRP = 0000040; /* R for group */ const S_IWGRP = 0000020; /* W for group */ const S_IXGRP = 0000010; /* X for group */ const S_IRWXO = 0000007; /* RWX mask for other */ const S_IROTH = 0000004; /* R for other */ const S_IWOTH = 0000002; /* W for other */ const S_IXOTH = 0000001; /* X for other */ const S_ISVTX = 0001000; /* save swapped text even after use */ public static function getExtFileAttr($attr) { return parent::getExtFileAttr($attr) << 16; } } abstract class DeflateStream { static public function create($level) { if (COMPR::NONE === $level) { return new DeflateStoreStream($level); } else { return new DeflatePeclStream($level); } } protected function __construct($level) {} abstract public function update($data); abstract public function finish(); } class DeflatePeclStream extends DeflateStream { private $peclDeflateStream; const PECL1_DEFLATE_STREAM_CLASS = '\HttpDeflateStream'; const PECL2_DEFLATE_STREAM_CLASS = '\http\encoding\Stream\Deflate'; protected function __construct($level) { $class = self::PECL1_DEFLATE_STREAM_CLASS; if (!class_exists($class)) { $class = self::PECL2_DEFLATE_STREAM_CLASS; } if (!class_exists($class)) { new \Exception('unable to instantiate PECL deflate stream (requires pecl_http >= 0.10)'); } $deflateFlags = constant($class . '::TYPE_RAW'); switch ($level) { case COMPR::NORMAL: $deflateFlags |= constant($class . '::LEVEL_DEF'); break; case COMPR::MAXIMUM: $deflateFlags |= constant($class . '::LEVEL_MAX'); break; case COMPR::SUPERFAST: $deflateFlags |= constant($class . '::LEVEL_MIN'); break; } $this->peclDeflateStream = new $class($deflateFlags); } public function update($data) { return $this->peclDeflateStream->update($data); } public function finish() { return $this->peclDeflateStream->finish(); } } class DeflateStoreStream extends DeflateStream { const BLOCK_HEADER_NORMAL = 0x00; const BLOCK_HEADER_FINAL = 0x01; const BLOCK_HEADER_ERROR = 0x03; const MAX_UNCOMPR_BLOCK_SIZE = 0xffff; public function update($data) { $result = ''; for ($pos = 0, $len = strlen($data); $pos < $len; $pos += self::MAX_UNCOMPR_BLOCK_SIZE) { $result .= $this->write_block(self::BLOCK_HEADER_NORMAL, substr($data, $pos, self::MAX_UNCOMPR_BLOCK_SIZE)); } return $result; } public function finish() { return $this->write_block(self::BLOCK_HEADER_FINAL, ''); } private function write_block($header, $data) { return '' . pack8($header) // block header 3 bits, null padding = 1 byte . pack16le(strlen($data)) // block data length 2 bytes . pack16le(0xffff ^ strlen($data)) // complement of block data size 2 bytes . $data // data . ''; } } class DOS extends ExtFileAttr { const READ_ONLY = 0x1; const HIDDEN = 0x2; const SYSTEM = 0x4; const VOLUME = 0x8; const DIR = 0x10; const ARCHIVE = 0x20; const RESERVED1 = 0x40; const RESERVED2 = 0x80; } class GPFLAGS { const NONE = 0x0000; // no flags set const COMP1 = 0x0002; // compression flag 1 (compression settings, see APPNOTE for details) const COMP2 = 0x0004; // compression flag 2 (compression settings, see APPNOTE for details) const ADD = 0x0008; // ADD flag (sizes and crc32 are append in data descriptor) const EFS = 0x0800; // EFS flag (UTF-8 encoded filename and/or comment) // compression settings for deflate/deflate64 const DEFL_NORM = 0x0000; // normal compression (COMP1 and COMP2 not set) const DEFL_MAX = COMP1; // maximum compression const DEFL_FAST = COMP2; // fast compression const DEFL_SFAST = 0x0006; // superfast compression (COMP1 and COMP2 set) }