destDirectory = $destDirectory; $writeInfo->isCompressed = $archiveHeader->isCompressed; if ($offset > 0) { fseek($archiveHandle, $offset); } $moreToRead = true; while ($moreToRead) { if ($writeInfo->currentFileHeader != null) { try { if (self::passesInclusionFilter($inclusionFilter, $writeInfo->currentFileHeader->relativePath)) { self::writeToFile($archiveHandle, $writeInfo); $writeInfo->fileWriteCount++; } elseif ($writeInfo->currentFileHeader->fileSize > 0) { self::skipFileInArchive($archiveHandle, $writeInfo->currentFileHeader); } $writeInfo->currentFileHeader = null; // Expand state taken care of within the write to file to ensure consistency } catch (Exception $ex) { if (!$ignoreErrors) { throw $ex; } } } else { $headerType = self::getNextHeaderType($archiveHandle); switch ($headerType) { case self::HEADER_TYPE_FILE: $writeInfo->currentFileHeader = DupArchiveReaderFileHeader::readFromArchive($archiveHandle, false, true); break; case self::HEADER_TYPE_DIR: $directoryHeader = DupArchiveReaderDirectoryHeader::readFromArchive($archiveHandle, true); // self::log("considering $inclusionFilter and {$directoryHeader->relativePath}"); if (self::passesInclusionFilter($inclusionFilter, $directoryHeader->relativePath)) { // self::log("passed"); $directory = "{$writeInfo->destDirectory}/{$directoryHeader->relativePath}"; // $mode = $directoryHeader->permissions; // rodo handle this more elegantly @mkdir($directory, $directoryHeader->permissions, true); if (is_callable(self::$mkdirCallback)) { call_user_func(self::$mkdirCallback, $directory, 'u+rwx', true); } else { mkdir($directory, 0755, true); } $writeInfo->directoryWriteCount++; } else { // self::log("didnt pass"); } break; case self::HEADER_TYPE_NONE: $moreToRead = false; } } } fclose($archiveHandle); } /** * Write to file * * @param resource $archiveHandle archive file handle * @param DupArchiveExpanderInfo $writeInfo write info * * @return void */ private static function writeToFile($archiveHandle, DupArchiveExpanderInfo $writeInfo) { $destFilePath = $writeInfo->getCurrentDestFilePath(); if ($writeInfo->currentFileHeader->fileSize > 0) { $parentDir = dirname($destFilePath); if (!file_exists($parentDir)) { if (is_callable(self::$mkdirCallback)) { $res = call_user_func(self::$mkdirCallback, $parentDir, 'u+rwx', true); } else { $res = mkdir($parentDir, 0755, true); } if (!$res) { throw new Exception("Couldn't create {$parentDir}"); } } $destFileHandle = fopen($destFilePath, 'wb+'); if ($destFileHandle === false) { throw new Exception("Couldn't open {$destFilePath} for writing."); } do { self::appendGlobToFile($archiveHandle, $destFileHandle, $writeInfo); $currentFileOffset = ftell($destFileHandle); $moreGlobstoProcess = $currentFileOffset < $writeInfo->currentFileHeader->fileSize; } while ($moreGlobstoProcess); fclose($destFileHandle); if (is_callable(self::$chmodCallback)) { call_user_func(self::$chmodCallback, $destFilePath, 'u+rw'); } else { chmod($destFilePath, 0644); } self::validateExpandedFile($writeInfo); } else { if (touch($destFilePath) === false) { throw new Exception("Couldn't create $destFilePath"); } if (is_callable(self::$chmodCallback)) { call_user_func(self::$chmodCallback, $destFilePath, 'u+rw'); } else { chmod($destFilePath, 0644); } } } /** * Validate file * * @param DupArchiveExpanderInfo $writeInfo write info * * @return void */ private static function validateExpandedFile(DupArchiveExpanderInfo $writeInfo) { if ($writeInfo->currentFileHeader->hash !== '00000000000000000000000000000000') { $hash = hash_file('crc32b', $writeInfo->getCurrentDestFilePath()); if ($hash !== $writeInfo->currentFileHeader->hash) { throw new Exception("MD5 validation fails for {$writeInfo->getCurrentDestFilePath()}"); } } } /** * Undocumented function * Assumption is that archive handle points to a glob header on this call * * @param resource $archiveHandle archive handle * @param resource $destFileHandle dest file handle * @param DupArchiveExpanderInfo $writeInfo write info * * @return void */ private static function appendGlobToFile($archiveHandle, $destFileHandle, DupArchiveExpanderInfo $writeInfo) { $globHeader = DupArchiveReaderGlobHeader::readFromArchive($archiveHandle, false); $globContents = fread($archiveHandle, $globHeader->storedSize); if ($globContents === false) { throw new Exception("Error reading glob from " . $writeInfo->getCurrentDestFilePath()); } if ($writeInfo->isCompressed) { $globContents = gzinflate($globContents); } if (fwrite($destFileHandle, $globContents) !== strlen($globContents)) { throw new Exception("Unable to write all bytes of data glob to storage."); } } /** * Check filter * * @param string $filter filter * @param string $candidate candidate * * @return bool */ private static function passesInclusionFilter($filter, $candidate) { return (substr($candidate, 0, strlen($filter)) == $filter); } }