/**
  * Adds a file entry
  *
  * @param   io.archive.zip.ZipFileEntry entry
  * @return  io.archive.zip.ZipFileEntry entry
  * @throws  lang.IllegalArgumentException in case the filename is longer than 65535 bytes
  */
 public function addFile(ZipFileEntry $entry)
 {
     $name = iconv(\xp::ENCODING, $this->unicode ? 'utf-8' : 'cp437', str_replace('\\', '/', $entry->getName()));
     $nameLength = strlen($name);
     if ($nameLength > 0xffff) {
         throw new IllegalArgumentException('Filename too long (' . $nameLength . ')');
     }
     $this->out && $this->out->close();
     if ($this->password) {
         $this->out = new CipheringZipFileOutputStream($this, $entry, $name, new ZipCipher($this->password));
     } else {
         $this->out = new ZipFileOutputStream($this, $entry, $name);
     }
     $entry->os = $this->out;
     return $entry;
 }
 /**
  * Gets current entry
  *
  * @return  io.archive.zip.ZipEntry
  * @throws  lang.FormatException
  * @throws  lang.IllegalArgumentException on password mismatch
  */
 public function currentEntry()
 {
     $type = $this->streamRead(4);
     switch ($type) {
         case self::FHDR:
             // Entry
             $header = unpack('vversion/vflags/vcompression/vtime/vdate/Vcrc/Vcompressed/Vuncompressed/vnamelen/vextralen', $this->streamRead(26));
             if (0 === $header['namelen']) {
                 // Prevent 0-length read.
                 $decoded = '';
             } else {
                 $name = (string) $this->streamRead($header['namelen']);
                 // Decode name from zipfile. If we find general purpose flag bit 11
                 // (EFS), the name is encoded in UTF-8, if not, we try the following:
                 // Decode from utf-8, then try cp437, and if that fails, we will use
                 // it as-is. Do this as certain vendors (Java e.g.) always use utf-8
                 // but do not indicate this via EFS.
                 $decoded = $this->decodeName($name, $header['flags'] & 2048 ? ['utf-8' => \xp::ENCODING] : ['utf-8' => \xp::ENCODING, 'cp437' => 'iso-8859-1', 'cp1252' => \xp::ENCODING]);
             }
             $extra = $this->streamRead($header['extralen']);
             $date = $this->dateFromDosDateTime($header['date'], $header['time']);
             $this->skip = $header['compressed'];
             // Short-circuit here for directories
             if ('/' === substr($name, -1)) {
                 $e = new ZipDirEntry($decoded);
                 $e->setLastModified($date);
                 $e->setSize($header['uncompressed']);
                 return $e;
             }
             // Bit 3: If this bit is set, the fields crc-32, compressed
             // size and uncompressed size are set to zero in the local
             // header.  The correct values are put in the data descriptor
             // immediately following the compressed data.
             if ($header['flags'] & 8) {
                 if (!isset($this->index[$name])) {
                     $position = $this->position;
                     $offset = $this->readCentralDirectory();
                     $this->streamPosition($position);
                 }
                 if (!isset($this->index[$name])) {
                     throw new FormatException('.zip archive broken: cannot find "' . $name . '" in central directory.');
                 }
                 $header = $this->index[$name];
                 // In case we're here, we can be sure to have a
                 // RandomAccessStream - otherwise the central directory
                 // could not have been read in the first place. So,
                 // we may seek.
                 // If we had strict type checking this would not be
                 // possible, though.
                 // The offset is relative to the file begin - but also skip over the usual parts:
                 // * file header signature (4 bytes)
                 // * file header (26 bytes)
                 // * file extra + file name (variable size)
                 $this->streamPosition($header['offset'] + 30 + $header['extralen'] + $header['namelen']);
                 // Set skip accordingly: 4 bytes data descriptor signature + 12 bytes data descriptor
                 $this->skip = $header['compressed'] + 16;
             }
             // Bit 1: The file is encrypted
             if ($header['flags'] & 1) {
                 $cipher = new ZipCipher($this->password);
                 $preamble = $cipher->decipher($this->streamRead(12));
                 // Verify
                 if (ord($preamble[11]) !== ($header['crc'] >> 24 & 0xff)) {
                     throw new IllegalArgumentException('The password did not match (' . ord($preamble[11]) . ' vs. ' . ($header['crc'] >> 24 & 0xff) . ')');
                 }
                 // Password matches.
                 $this->skip -= 12;
                 $header['compressed'] -= 12;
                 $is = new DecipheringInputStream(new ZipFileInputStream($this, $this->position, $header['compressed']), $cipher);
             } else {
                 $is = new ZipFileInputStream($this, $this->position, $header['compressed']);
             }
             // Create ZipEntry object and return it
             $e = new ZipFileEntry($decoded);
             $e->setLastModified($date);
             $e->setSize($header['uncompressed']);
             $e->setCompression(Compression::getInstance($header['compression']));
             $e->is = $is;
             return $e;
         case self::DHDR:
             // Zip directory
             return null;
             // XXX: For the moment, ignore directory and stop here
         case self::EOCD:
             // End of central directory
             return null;
     }
     throw new FormatException('Unknown byte sequence ' . addcslashes($type, ".."));
 }