protected function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
{
$v_result = 1;
// Read the file header
if (($v_result = $this->privReadFileHeader($v_header)) != 1) {
return $v_result;
}
// Check that the file header is coherent with $p_entry info
if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
// TBC
}
// Look for all path to remove
if ($p_remove_all_path == true) {
// Look for folder entry that not need to be extracted
if (($p_entry['external'] & 0x10) == 0x10) {
$p_entry['status'] = "filtered";
return $v_result;
}
// Get the basename of the path
$p_entry['filename'] = basename($p_entry['filename']);
} else {
if ($p_remove_path != '') {
// Look for path to remove
if (self::UtilPathInclusion($p_remove_path, $p_entry['filename']) == 2) {
// Change the file status
$p_entry['status'] = 'filtered';
// Return
return $v_result;
}
$p_remove_path_size = strlen($p_remove_path);
if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path) {
// Remove the path
$p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size);
}
}
}
// Add the path
if ($p_path != '') {
$p_entry['filename'] = $p_path . "/" . $p_entry['filename'];
}
// Check a base_dir_restriction
if (isset($p_options[self::OPT_EXTRACT_DIR_RESTRICTION])) {
$v_inclusion = self::UtilPathInclusion($p_options[self::OPT_EXTRACT_DIR_RESTRICTION], $p_entry['filename']);
if ($v_inclusion == 0) {
$this->privErrorLog(self::ERR_DIRECTORY_RESTRICTION, 'Filename \'' . $p_entry['filename'] . '\' is outside ' . self::OPT_EXTRACT_DIR_RESTRICTION);
return self::errorCode();
}
}
// Look for pre-extract callback
if (isset($p_options[self::CB_PRE_EXTRACT])) {
// Generate a local information
$v_local_header = array();
$this->privConvertHeader2FileInfo($p_entry, $v_local_header);
// Call the callback
// Here I do not use call_user_func() because I need to send a reference to the header.
// eval('$v_result = '.$p_options[self::CB_PRE_EXTRACT].'(self::CB_PRE_EXTRACT, $v_local_header);');
$v_result = $p_options[self::CB_PRE_EXTRACT](self::CB_PRE_EXTRACT, $v_local_header);
if ($v_result == 0) {
// Change the file status
$p_entry['status'] = 'skipped';
$v_result = 1;
}
// Look for abort result
if ($v_result == 2) {
// This status is internal and will be changed in 'skipped'
$p_entry['status'] = 'aborted';
$v_result = self::ERR_USER_ABORTED;
}
// Update the informations
// Only some fields can be modified
$p_entry['filename'] = $v_local_header['filename'];
}
// Look if extraction should be done
if ($p_entry['status'] == 'ok') {
// Look for specific actions while the file exist
if (file_exists($p_entry['filename'])) {
// Look if file is a directory
if (is_dir($p_entry['filename'])) {
// Change the file status
$p_entry['status'] = 'already_a_directory';
// Look for self::OPT_STOP_ON_ERROR
// For historical reason first PclZip implementation does not stop
// when this kind of error occurs.
if (isset($p_options[self::OPT_STOP_ON_ERROR]) && $p_options[self::OPT_STOP_ON_ERROR] === true) {
$this->privErrorLog(self::ERR_ALREADY_A_DIRECTORY, 'Filename \'' . $p_entry['filename'] . '\' is already used by an existing directory');
return PclZip::errorCode();
}
} else {
if (!is_writeable($p_entry['filename'])) {
// Look if file is write protected
// Change the file status
$p_entry['status'] = "write_protected";
// Look for self::OPT_STOP_ON_ERROR
// For historical reason first PclZip implementation does not stop
// when this kind of error occurs.
if (isset($p_options[self::OPT_STOP_ON_ERROR]) && $p_options[self::OPT_STOP_ON_ERROR] === true) {
$this->privErrorLog(self::ERR_WRITE_OPEN_FAIL, 'Filename \'' . $p_entry['filename'] . '\' exists and is write protected');
return PclZip::errorCode();
}
} else {
if (filemtime($p_entry['filename']) > $p_entry['mtime']) {
// Look if the extracted file is older
// Change the file status
if (isset($p_options[self::OPT_REPLACE_NEWER]) && $p_options[self::OPT_REPLACE_NEWER] === true) {
// do nothing
} else {
$p_entry['status'] = 'newer_exist';
// Look for self::OPT_STOP_ON_ERROR
// For historical reason first PclZip implementation does not stop
// when this kind of error occurs.
if (isset($p_options[self::OPT_STOP_ON_ERROR]) && $p_options[self::OPT_STOP_ON_ERROR] === true) {
$this->privErrorLog(self::ERR_WRITE_OPEN_FAIL, 'Newer version of \'' . $p_entry['filename'] . '\' exists and option PCLZIP_OPT_REPLACE_NEWER is not selected');
return self::errorCode();
}
}
} else {
}
}
}
} else {
// Check the directory availability and create it if necessary
if (($p_entry['external'] & 0x10) == 0x10 || substr($p_entry['filename'], -1) == '/') {
$v_dir_to_check = $p_entry['filename'];
} else {
if (!strstr($p_entry['filename'], "/")) {
$v_dir_to_check = "";
} else {
$v_dir_to_check = dirname($p_entry['filename']);
}
}
if (($v_result = $this->privDirCheck($v_dir_to_check, ($p_entry['external'] & 0x10) == 0x10)) != 1) {
// Change the file status
$p_entry['status'] = 'path_creation_fail';
//return $v_result;
$v_result = 1;
}
}
}
// Look if extraction should be done
if ($p_entry['status'] == 'ok') {
// Do the extraction (if not a folder)
if (!(($p_entry['external'] & 0x10) == 0x10)) {
// Look for not compressed file
if ($p_entry['compression'] == 0) {
// Opening destination file
if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
// Change the file status
$p_entry['status'] = 'write_error';
return $v_result;
}
// Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
$v_size = $p_entry['compressed_size'];
while ($v_size != 0) {
$v_read_size = $v_size < self::READ_BLOCK_SIZE ? $v_size : self::READ_BLOCK_SIZE;
$v_buffer = @fread($this->zip_fd, $v_read_size);
// $v_binary_data = pack('a'.$v_read_size, $v_buffer);
// @fwrite($v_dest_file, $v_binary_data, $v_read_size);
@fwrite($v_dest_file, $v_buffer, $v_read_size);
$v_size -= $v_read_size;
}
// Closing the destination file
fclose($v_dest_file);
// Change the file mtime
touch($p_entry['filename'], $p_entry['mtime']);
} else {
// TBC
// Need to be finished
if (($p_entry['flag'] & 1) == 1) {
$this->privErrorLog(self::ERR_UNSUPPORTED_ENCRYPTION, 'File \'' . $p_entry['filename'] . '\' is encrypted. Encrypted files are not supported.');
return self::errorCode();
}
// Look for using temporary file to unzip
if (!isset($p_options[self::OPT_TEMP_FILE_OFF]) && (isset($p_options[self::OPT_TEMP_FILE_ON]) || isset($p_options[self::OPT_TEMP_FILE_THRESHOLD]) && $p_options[self::OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])) {
$v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options);
if ($v_result < self::ERR_NO_ERROR) {
return $v_result;
}
} else {
// Look for extract in memory
// Read the compressed file in a buffer (one shot)
$v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
// Decompress the file
$v_file_content = @gzinflate($v_buffer);
unset($v_buffer);
if ($v_file_content === false) {
// Change the file status
// TBC
$p_entry['status'] = "error";
return $v_result;
}
// Opening destination file
if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
// Change the file status
$p_entry['status'] = "write_error";
return $v_result;
}
// Write the uncompressed data
@fwrite($v_dest_file, $v_file_content, $p_entry['size']);
unset($v_file_content);
// Closing the destination file
@fclose($v_dest_file);
}
// Change the file mtime
@touch($p_entry['filename'], $p_entry['mtime']);
}
// Look for chmod option
if (isset($p_options[self::OPT_SET_CHMOD])) {
// Change the mode of the file
@chmod($p_entry['filename'], $p_options[self::OPT_SET_CHMOD]);
}
}
}
// Change abort status
if ($p_entry['status'] == "aborted") {
$p_entry['status'] = "skipped";
} elseif (isset($p_options[self::CB_POST_EXTRACT])) {
// Look for post-extract callback
// Generate a local information
$v_local_header = array();
$this->privConvertHeader2FileInfo($p_entry, $v_local_header);
// Call the callback
// Here I do not use call_user_func() because I need to send a reference to the header.
// eval('$v_result = '.$p_options[self::CB_POST_EXTRACT].'(self::CB_POST_EXTRACT, $v_local_header);');
$v_result = $p_options[self::CB_POST_EXTRACT](self::CB_POST_EXTRACT, $v_local_header);
// Look for abort result
if ($v_result == 2) {
$v_result = self::ERR_USER_ABORTED;
}
}
return $v_result;
}