/** * 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, "..")); }