Initial commit: WordPress wp-content (themes, plugins, languages)
- Theme: momentry (custom theme with REST API routes) - Plugins: code-snippets (contains all API proxies) - Languages: zh_TW translations - Excludes: cache, backups, uploads, logs
This commit is contained in:
432
plugins/all-in-one-wp-migration/lib/vendor/servmask/archiver/class-ai1wm-archiver.php
vendored
Normal file
432
plugins/all-in-one-wp-migration/lib/vendor/servmask/archiver/class-ai1wm-archiver.php
vendored
Normal file
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2014-2025 ServMask Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Attribution: This code is part of the All-in-One WP Migration plugin, developed by
|
||||
*
|
||||
* ███████╗███████╗██████╗ ██╗ ██╗███╗ ███╗ █████╗ ███████╗██╗ ██╗
|
||||
* ██╔════╝██╔════╝██╔══██╗██║ ██║████╗ ████║██╔══██╗██╔════╝██║ ██╔╝
|
||||
* ███████╗█████╗ ██████╔╝██║ ██║██╔████╔██║███████║███████╗█████╔╝
|
||||
* ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██║╚██╔╝██║██╔══██║╚════██║██╔═██╗
|
||||
* ███████║███████╗██║ ██║ ╚████╔╝ ██║ ╚═╝ ██║██║ ██║███████║██║ ██╗
|
||||
* ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
die( 'Kangaroos cannot jump here' );
|
||||
}
|
||||
|
||||
abstract class Ai1wm_Archiver {
|
||||
|
||||
const HEADER_SIZE = 4377;
|
||||
|
||||
const READ_CHUNK_SIZE = 512000;
|
||||
|
||||
/**
|
||||
* File name including path to the file
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
protected $file_name = null;
|
||||
|
||||
/**
|
||||
* File password string
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
protected $file_password = null;
|
||||
|
||||
/**
|
||||
* File compression type
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
protected $file_compression = null;
|
||||
|
||||
/**
|
||||
* Handle to the file
|
||||
*
|
||||
* @type resource
|
||||
*/
|
||||
protected $file_handle = null;
|
||||
|
||||
/**
|
||||
* Header block format of a file
|
||||
*
|
||||
* Field Name Offset Length Contents
|
||||
* name 0 255 filename (no path, no slash)
|
||||
* size 255 14 size of file contents
|
||||
* mtime 269 12 last modification time
|
||||
* prefix 281 4088 path name, no trailing slashes
|
||||
* crc32 4369 8 CRC32 checksum (hex string, optional)
|
||||
*
|
||||
* @type array
|
||||
*/
|
||||
protected $block_format = array(
|
||||
'a255', // filename
|
||||
'a14', // size of file contents
|
||||
'a12', // last time modified
|
||||
'a4088', // path
|
||||
'a8', // crc32
|
||||
);
|
||||
|
||||
/**
|
||||
* Archive CRC value from the v2 EOF block
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
protected $archive_crc_value = null;
|
||||
|
||||
/**
|
||||
* Archive CRC size from the v2 EOF block
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
protected $archive_crc_size = null;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*
|
||||
* Initializes filename and end of file block
|
||||
*
|
||||
* @param string $file_name File to use as archive
|
||||
* @param string $file_password File password string
|
||||
* @param string $file_compression File compression type
|
||||
* @param bool $file_write File Read/write mode
|
||||
*
|
||||
* @throws Ai1wm_Not_Accessible_Exception
|
||||
* @throws Ai1wm_Not_Seekable_Exception
|
||||
*/
|
||||
public function __construct( $file_name, $file_password = null, $file_compression = null, $file_write = false ) {
|
||||
$this->file_name = $file_name;
|
||||
$this->file_password = $file_password;
|
||||
$this->file_compression = $file_compression;
|
||||
|
||||
// Open archive file
|
||||
if ( $file_write ) {
|
||||
// Open archive file for writing
|
||||
if ( ( $this->file_handle = @fopen( $file_name, 'cb' ) ) === false ) {
|
||||
throw new Ai1wm_Not_Accessible_Exception( sprintf( __( 'Could not open file for writing. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Seek to end of archive file
|
||||
if ( @fseek( $this->file_handle, 0, SEEK_END ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to end of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
} else {
|
||||
// Open archive file for reading
|
||||
if ( ( $this->file_handle = @fopen( $file_name, 'rb' ) ) === false ) {
|
||||
throw new Ai1wm_Not_Accessible_Exception( sprintf( __( 'Could not open file for reading. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current file pointer
|
||||
*
|
||||
* @param int $offset Archive offset
|
||||
*
|
||||
* @throws \Ai1wm_Not_Seekable_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_file_pointer( $offset ) {
|
||||
if ( @fseek( $this->file_handle, $offset, SEEK_SET ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $offset ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current file pointer
|
||||
*
|
||||
* @throws \Ai1wm_Not_Tellable_Exception
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_file_pointer() {
|
||||
if ( ( $offset = @ftell( $this->file_handle ) ) === false ) {
|
||||
throw new Ai1wm_Not_Tellable_Exception( sprintf( __( 'Could not tell offset of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends end of file block to the archive file
|
||||
*
|
||||
* @param string|null $archive_crc_value Pre-calculated archive CRC32 (optional)
|
||||
*
|
||||
* @throws \Ai1wm_Not_Seekable_Exception
|
||||
* @throws \Ai1wm_Not_Writable_Exception
|
||||
* @throws \Ai1wm_Quota_Exceeded_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function append_eof( $archive_crc_value = null ) {
|
||||
// Seek to end of archive file
|
||||
if ( @fseek( $this->file_handle, 0, SEEK_END ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to end of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Use pre-calculated CRC if provided, otherwise calculate (fallback)
|
||||
if ( empty( $archive_crc_value ) ) {
|
||||
$archive_crc_value = Ai1wm_Crc::calculate_file_crc32( $this->file_name );
|
||||
}
|
||||
|
||||
// Get archive size (before EOF block)
|
||||
if ( ( $archive_crc_size = @ftell( $this->file_handle ) ) === false ) {
|
||||
throw new Ai1wm_Not_Tellable_Exception( sprintf( __( 'Could not tell offset of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Write end of file block
|
||||
if ( ( $eof_block = $this->get_eof_block( $archive_crc_size, $archive_crc_value ) ) ) {
|
||||
if ( ( $file_bytes = @fwrite( $this->file_handle, $eof_block ) ) !== false ) {
|
||||
if ( strlen( $eof_block ) !== $file_bytes ) {
|
||||
throw new Ai1wm_Quota_Exceeded_Exception( sprintf( __( 'Out of disk space. Could not write end of block to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
} else {
|
||||
throw new Ai1wm_Not_Writable_Exception( sprintf( __( 'Could not write end of block to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace forward slash with current directory separator
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function replace_forward_slash_with_directory_separator( $path ) {
|
||||
return str_replace( '/', DIRECTORY_SEPARATOR, $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace current directory separator with forward slash
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function replace_directory_separator_with_forward_slash( $path ) {
|
||||
return str_replace( DIRECTORY_SEPARATOR, '/', $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape Windows directory separator
|
||||
*
|
||||
* @param string $path Path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function escape_windows_directory_separator( $path ) {
|
||||
return preg_replace( '/[\\\\]+/', '\\\\\\\\', $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate archive file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_valid() {
|
||||
// Failed detecting the current file pointer offset
|
||||
if ( ( $offset = @ftell( $this->file_handle ) ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Failed seeking the beginning of EOL block
|
||||
if ( @fseek( $this->file_handle, -static::HEADER_SIZE, SEEK_END ) === -1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get end of file block
|
||||
if ( ( $block = @fread( $this->file_handle, static::HEADER_SIZE ) ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Failed returning to original offset
|
||||
if ( @fseek( $this->file_handle, $offset, SEEK_SET ) === -1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trailing block does not match EOL
|
||||
if ( $this->is_eof_block( $block ) === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates the archive file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function truncate() {
|
||||
if ( ( $offset = @ftell( $this->file_handle ) ) === false ) {
|
||||
throw new Ai1wm_Not_Tellable_Exception( sprintf( __( 'Could not tell offset of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
if ( @filesize( $this->file_name ) > $offset ) {
|
||||
if ( @ftruncate( $this->file_handle, $offset ) === false ) {
|
||||
throw new Ai1wm_Not_Truncatable_Exception( sprintf( __( 'Could not truncate file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the archive file
|
||||
*
|
||||
* We either close the file or append the end of file block if complete argument is set to true
|
||||
*
|
||||
* @param bool $complete Flag to append end of file block
|
||||
* @param string|null $archive_crc_value Pre-calculated archive CRC32 (optional)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close( $complete = false, $archive_crc_value = null ) {
|
||||
// Are we done appending to the file?
|
||||
if ( true === $complete ) {
|
||||
$this->append_eof( $archive_crc_value );
|
||||
}
|
||||
|
||||
if ( @fclose( $this->file_handle ) === false ) {
|
||||
throw new Ai1wm_Not_Closable_Exception( sprintf( __( 'Could not close file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate end of file block
|
||||
*
|
||||
* @param string $archive_crc_value Archive CRC
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_eof_block( $archive_crc_size = null, $archive_crc_value = null ) {
|
||||
return pack( 'a255a14a4100a8', '', $archive_crc_size, '', $archive_crc_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a block is an end of file block (v1 or v2)
|
||||
*
|
||||
* @param string $block The block to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_eof_block( $block ) {
|
||||
return $this->is_v1_eof( $block ) || $this->is_v2_eof( $block );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a block is a v1 end of file block (all null bytes)
|
||||
*
|
||||
* @param string $block The block to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_v1_eof( $block ) {
|
||||
return $this->get_eof_block() === $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a block is a v2 end of file block
|
||||
*
|
||||
* @param string $block The block to check
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_v2_eof( $block ) {
|
||||
// Unpack end of file data
|
||||
if ( ( $data = unpack( 'a255/a14size/a4100/a8crc32', $block ) ) ) {
|
||||
if ( isset( $data['size'], $data['crc32'] ) ) {
|
||||
if ( preg_match( '/^[0-9a-f]{8}$/i', $data['crc32'] ) ) {
|
||||
return $this->get_eof_block( $data['size'], $data['crc32'] ) === $block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archive CRC from EOF block (v2 only)
|
||||
*
|
||||
* @return string|null CRC32 hex string or null if v1 archive
|
||||
*/
|
||||
public function get_archive_crc_value() {
|
||||
if ( is_null( $this->archive_crc_value ) ) {
|
||||
$this->set_archive_crc_data();
|
||||
}
|
||||
|
||||
return $this->archive_crc_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get archive CRC size from EOF block (v2 only)
|
||||
*
|
||||
* @return int|null Size hex string or null if v1 archive
|
||||
*/
|
||||
public function get_archive_crc_size() {
|
||||
if ( is_null( $this->archive_crc_size ) ) {
|
||||
$this->set_archive_crc_data();
|
||||
}
|
||||
|
||||
return $this->archive_crc_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set archive CRC value and size from the v2 EOF block
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_archive_crc_data() {
|
||||
// Failed detecting the current file pointer offset
|
||||
if ( ( $offset = @ftell( $this->file_handle ) ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Failed seeking the beginning of EOL block
|
||||
if ( @fseek( $this->file_handle, -static::HEADER_SIZE, SEEK_END ) === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get end of file block
|
||||
if ( ( $block = @fread( $this->file_handle, static::HEADER_SIZE ) ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Failed returning to original offset
|
||||
if ( @fseek( $this->file_handle, $offset, SEEK_SET ) === -1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if v2 EOF
|
||||
if ( $this->is_v2_eof( $block ) === false ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unpack end of file data
|
||||
if ( ( $data = unpack( 'a255/a14size/a4100/a8crc32', $block ) ) ) {
|
||||
if ( isset( $data['crc32'] ) ) {
|
||||
$this->archive_crc_value = trim( $data['crc32'] );
|
||||
}
|
||||
|
||||
if ( isset( $data['size'] ) ) {
|
||||
$this->archive_crc_size = (int) trim( $data['size'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
306
plugins/all-in-one-wp-migration/lib/vendor/servmask/archiver/class-ai1wm-compressor.php
vendored
Normal file
306
plugins/all-in-one-wp-migration/lib/vendor/servmask/archiver/class-ai1wm-compressor.php
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2014-2025 ServMask Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Attribution: This code is part of the All-in-One WP Migration plugin, developed by
|
||||
*
|
||||
* ███████╗███████╗██████╗ ██╗ ██╗███╗ ███╗ █████╗ ███████╗██╗ ██╗
|
||||
* ██╔════╝██╔════╝██╔══██╗██║ ██║████╗ ████║██╔══██╗██╔════╝██║ ██╔╝
|
||||
* ███████╗█████╗ ██████╔╝██║ ██║██╔████╔██║███████║███████╗█████╔╝
|
||||
* ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██║╚██╔╝██║██╔══██║╚════██║██╔═██╗
|
||||
* ███████║███████╗██║ ██║ ╚████╔╝ ██║ ╚═╝ ██║██║ ██║███████║██║ ██╗
|
||||
* ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
die( 'Kangaroos cannot jump here' );
|
||||
}
|
||||
|
||||
class Ai1wm_Compressor extends Ai1wm_Archiver {
|
||||
|
||||
/**
|
||||
* Overloaded constructor that opens the passed file for writing
|
||||
*
|
||||
* @param string $file_name File to use as archive
|
||||
* @param string $file_password File password string
|
||||
* @param string $file_compression File compression type
|
||||
*/
|
||||
public function __construct( $file_name, $file_password = null, $file_compression = null ) {
|
||||
// Call parent, to initialize variables
|
||||
parent::__construct( $file_name, $file_password, $file_compression, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a file to the archive
|
||||
*
|
||||
* @param string $file_name File to add to the archive
|
||||
* @param string $new_file_name Write the file with a different name
|
||||
* @param int $file_bytes_read Amount of the bytes we read
|
||||
* @param int $file_bytes_offset File bytes offset
|
||||
* @param int $file_bytes_written Amount of the bytes we wrote
|
||||
* @param string|null $file_crc File CRC32 checksum (passed by reference, optional)
|
||||
*
|
||||
* @throws \Ai1wm_Not_Seekable_Exception
|
||||
* @throws \Ai1wm_Not_Writable_Exception
|
||||
* @throws \Ai1wm_Quota_Exceeded_Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function add_file( $file_name, $new_file_name = '', &$file_bytes_read = 0, &$file_bytes_offset = 0, &$file_bytes_written = 0, &$file_crc = null ) {
|
||||
// Replace forward slash with current directory separator in file name
|
||||
$file_name = ai1wm_replace_forward_slash_with_directory_separator( $file_name );
|
||||
|
||||
// Escape Windows directory separator in file name
|
||||
$file_name = ai1wm_escape_windows_directory_separator( $file_name );
|
||||
|
||||
// Flag to hold if file data has been processed
|
||||
$completed = true;
|
||||
|
||||
// Start time
|
||||
$start = microtime( true );
|
||||
|
||||
// Open the file for reading in binary mode (fopen may return null for quarantined files)
|
||||
if ( ( $file_handle = @fopen( $file_name, 'rb' ) ) ) {
|
||||
|
||||
// Start native hash for current chunk
|
||||
$hash_ctx = Ai1wm_Crc::init_crc32();
|
||||
|
||||
// Get header block with empty CRC placeholder
|
||||
if ( ( $block = $this->get_file_block( $file_name, $new_file_name, '' ) ) ) {
|
||||
|
||||
// Write header block
|
||||
if ( $file_bytes_offset === 0 ) {
|
||||
if ( ( $file_bytes = @fwrite( $this->file_handle, $block ) ) !== false ) {
|
||||
if ( strlen( $block ) !== $file_bytes ) {
|
||||
throw new Ai1wm_Quota_Exceeded_Exception( sprintf( __( 'Out of disk space. Could not write header to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
} else {
|
||||
throw new Ai1wm_Not_Writable_Exception( sprintf( __( 'Could not write header to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Set file offset
|
||||
if ( @fseek( $file_handle, $file_bytes_offset, SEEK_SET ) !== -1 ) {
|
||||
$file_bytes_read = 0;
|
||||
|
||||
// Cache config file check outside the loop
|
||||
$should_process_file = ! in_array( $new_file_name, ai1wm_config_filters() );
|
||||
|
||||
// Read the file in 512KB chunks
|
||||
while ( false === @feof( $file_handle ) ) {
|
||||
if ( ( $file_content = @fread( $file_handle, static::READ_CHUNK_SIZE ) ) !== false ) {
|
||||
|
||||
// Empty read indicates EOF
|
||||
if ( strlen( $file_content ) === 0 ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the amount of bytes we read
|
||||
$file_bytes_read += strlen( $file_content );
|
||||
|
||||
// Update CRC with original content (BEFORE compression/encryption)
|
||||
Ai1wm_Crc::update_crc32( $hash_ctx, $file_content );
|
||||
|
||||
// Do not encrypt or compress config files
|
||||
if ( $should_process_file === true ) {
|
||||
|
||||
// Add chunk data compression
|
||||
if ( ! empty( $this->file_compression ) ) {
|
||||
switch ( $this->file_compression ) {
|
||||
case 'gzip':
|
||||
$file_content = gzcompress( $file_content, 9 );
|
||||
break;
|
||||
|
||||
case 'bzip2':
|
||||
$file_content = bzcompress( $file_content, 9 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add chunk data encryption
|
||||
if ( ! empty( $this->file_password ) ) {
|
||||
$file_content = ai1wm_encrypt_string( $file_content, $this->file_password );
|
||||
}
|
||||
|
||||
// Add variable length chunk size before chunk data
|
||||
if ( ! empty( $this->file_compression ) ) {
|
||||
$file_content = pack( 'N', strlen( $file_content ) ) . $file_content;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ( $file_bytes = @fwrite( $this->file_handle, $file_content ) ) !== false ) {
|
||||
if ( strlen( $file_content ) !== $file_bytes ) {
|
||||
throw new Ai1wm_Quota_Exceeded_Exception( sprintf( __( 'Out of disk space. Could not write content to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
} else {
|
||||
throw new Ai1wm_Not_Writable_Exception( sprintf( __( 'Could not write content to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Add the amount of bytes we wrote
|
||||
$file_bytes_written += $file_bytes;
|
||||
}
|
||||
|
||||
// Time elapsed
|
||||
if ( ( $timeout = apply_filters( 'ai1wm_completed_timeout', 10 ) ) ) {
|
||||
if ( ( microtime( true ) - $start ) > $timeout ) {
|
||||
$completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the amount of bytes we read
|
||||
$file_bytes_offset += $file_bytes_read;
|
||||
}
|
||||
|
||||
// Combine and finalize CRC
|
||||
if ( empty( $file_crc ) ) {
|
||||
$file_crc = Ai1wm_Crc::finalize_crc32( $hash_ctx );
|
||||
} else {
|
||||
$file_crc = Ai1wm_Crc::combine_crc32( $file_crc, Ai1wm_Crc::finalize_crc32( $hash_ctx ), $file_bytes_read );
|
||||
}
|
||||
|
||||
// Write file size to file header
|
||||
if ( ( $file_size_block = $this->get_file_size_block( $file_bytes_written ) ) ) {
|
||||
|
||||
// Seek to beginning of file size (back over: content + crc32(8) + path(4088) + mtime(12) + size(14))
|
||||
if ( @fseek( $this->file_handle, - $file_bytes_written - 8 - 4088 - 12 - 14, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( __( 'Your PHP is 32-bit. In order to export your file, please change your PHP version to 64-bit and try again. <a href="https://help.servmask.com/knowledgebase/php-32bit/" target="_blank">Technical details</a>', 'all-in-one-wp-migration' ) );
|
||||
}
|
||||
|
||||
// Write file size to file header
|
||||
if ( ( $file_bytes = @fwrite( $this->file_handle, $file_size_block ) ) !== false ) {
|
||||
if ( strlen( $file_size_block ) !== $file_bytes ) {
|
||||
throw new Ai1wm_Quota_Exceeded_Exception( sprintf( __( 'Out of disk space. Could not write size to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
} else {
|
||||
throw new Ai1wm_Not_Writable_Exception( sprintf( __( 'Could not write size to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Seek to beginning of file CRC (forward over: mtime(12) + path(4088))
|
||||
if ( @fseek( $this->file_handle, + 12 + 4088, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( __( 'Your PHP is 32-bit. In order to export your file, please change your PHP version to 64-bit and try again. <a href="https://help.servmask.com/knowledgebase/php-32bit/" target="_blank">Technical details</a>', 'all-in-one-wp-migration' ) );
|
||||
}
|
||||
|
||||
// Write file CRC to file header
|
||||
if ( ( $file_crc_block = $this->get_file_crc_block( $file_crc ) ) ) {
|
||||
if ( ( $file_bytes = @fwrite( $this->file_handle, $file_crc_block ) ) !== false ) {
|
||||
if ( strlen( $file_crc_block ) !== $file_bytes ) {
|
||||
throw new Ai1wm_Quota_Exceeded_Exception( sprintf( __( 'Out of disk space. Could not write CRC to file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Seek to end of file content (forward over: content)
|
||||
if ( @fseek( $this->file_handle, + $file_bytes_written, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( __( 'Your PHP is 32-bit. In order to export your file, please change your PHP version to 64-bit and try again. <a href="https://help.servmask.com/knowledgebase/php-32bit/" target="_blank">Technical details</a>', 'all-in-one-wp-migration' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the handle
|
||||
@fclose( $file_handle );
|
||||
}
|
||||
|
||||
return $completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate binary block header for a file
|
||||
*
|
||||
* @param string $file_name Filename to generate block header for
|
||||
* @param string $new_file_name Write the file with a different name
|
||||
* @param string|null $crc32 CRC32 checksum (optional)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function get_file_block( $file_name, $new_file_name = '', $crc32 = null ) {
|
||||
$block = '';
|
||||
|
||||
// Get stats about the file
|
||||
if ( ( $stat = @stat( $file_name ) ) !== false ) {
|
||||
|
||||
// Filename of the file we are accessing
|
||||
if ( empty( $new_file_name ) ) {
|
||||
$name = ai1wm_basename( $file_name );
|
||||
} else {
|
||||
$name = ai1wm_basename( $new_file_name );
|
||||
}
|
||||
|
||||
// Size in bytes of the file
|
||||
$size = $stat['size'];
|
||||
|
||||
// Last time the file was modified
|
||||
$date = $stat['mtime'];
|
||||
|
||||
// Replace current directory separator with backward slash in file path
|
||||
if ( empty( $new_file_name ) ) {
|
||||
$path = ai1wm_replace_directory_separator_with_forward_slash( ai1wm_dirname( $file_name ) );
|
||||
} else {
|
||||
$path = ai1wm_replace_directory_separator_with_forward_slash( ai1wm_dirname( $new_file_name ) );
|
||||
}
|
||||
|
||||
// Only calculate CRC if not provided
|
||||
if ( empty( $crc32 ) ) {
|
||||
$crc32 = Ai1wm_Crc::calculate_file_crc32( $file_name );
|
||||
}
|
||||
|
||||
// Concatenate block format parts
|
||||
$format = implode( '', $this->block_format );
|
||||
|
||||
// Pack file data into binary string
|
||||
$block = pack( $format, $name, $size, $date, $path, $crc32 );
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate file size binary block header for a file
|
||||
*
|
||||
* @param int $file_size File size
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_size_block( $file_size ) {
|
||||
$block = '';
|
||||
|
||||
// Pack file data into binary string
|
||||
if ( isset( $this->block_format[1] ) ) {
|
||||
$block = pack( $this->block_format[1], $file_size );
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate file CRC binary block header for a file
|
||||
*
|
||||
* @param int $file_crc File CRC
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_file_crc_block( $file_crc ) {
|
||||
$block = '';
|
||||
|
||||
// Pack file data into binary string
|
||||
if ( isset( $this->block_format[4] ) ) {
|
||||
$block = pack( $this->block_format[4], $file_crc );
|
||||
}
|
||||
|
||||
return $block;
|
||||
}
|
||||
}
|
||||
717
plugins/all-in-one-wp-migration/lib/vendor/servmask/archiver/class-ai1wm-extractor.php
vendored
Normal file
717
plugins/all-in-one-wp-migration/lib/vendor/servmask/archiver/class-ai1wm-extractor.php
vendored
Normal file
@@ -0,0 +1,717 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2014-2025 ServMask Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Attribution: This code is part of the All-in-One WP Migration plugin, developed by
|
||||
*
|
||||
* ███████╗███████╗██████╗ ██╗ ██╗███╗ ███╗ █████╗ ███████╗██╗ ██╗
|
||||
* ██╔════╝██╔════╝██╔══██╗██║ ██║████╗ ████║██╔══██╗██╔════╝██║ ██╔╝
|
||||
* ███████╗█████╗ ██████╔╝██║ ██║██╔████╔██║███████║███████╗█████╔╝
|
||||
* ╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██║╚██╔╝██║██╔══██║╚════██║██╔═██╗
|
||||
* ███████║███████╗██║ ██║ ╚████╔╝ ██║ ╚═╝ ██║██║ ██║███████║██║ ██╗
|
||||
* ╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
die( 'Kangaroos cannot jump here' );
|
||||
}
|
||||
|
||||
class Ai1wm_Extractor extends Ai1wm_Archiver {
|
||||
|
||||
/**
|
||||
* Total files count
|
||||
*
|
||||
* @type int
|
||||
*/
|
||||
protected $total_files_count = null;
|
||||
|
||||
/**
|
||||
* Total files size
|
||||
*
|
||||
* @type int
|
||||
*/
|
||||
protected $total_files_size = null;
|
||||
|
||||
/**
|
||||
* Overloaded constructor that opens the passed file for reading
|
||||
*
|
||||
* @param string $file_name File to use as archive
|
||||
* @param string $file_password File password string
|
||||
* @param string $file_compression File compression type
|
||||
*/
|
||||
public function __construct( $file_name, $file_password = null, $file_compression = null ) {
|
||||
// Call parent, to initialize variables
|
||||
parent::__construct( $file_name, $file_password, $file_compression, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* List all files in the archive
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function list_files() {
|
||||
$files = array();
|
||||
|
||||
// Seek to beginning of archive file
|
||||
if ( @fseek( $this->file_handle, 0, SEEK_SET ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to beginning of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
$offset = 0;
|
||||
|
||||
// Loop over files
|
||||
while ( ( $block = @fread( $this->file_handle, static::HEADER_SIZE ) ) ) {
|
||||
|
||||
// End block has been reached
|
||||
if ( $this->is_eof_block( $block ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get file data from the block
|
||||
if ( ( $data = $this->get_data_from_block( $block ) ) ) {
|
||||
// Store the position where the file begins - used for downloading from archive directly
|
||||
$data['offset'] = $offset;
|
||||
|
||||
// Skip file content, so we can move forward to the next file
|
||||
if ( @fseek( $this->file_handle, $data['size'], SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $data['size'] ) );
|
||||
}
|
||||
|
||||
$files[] = $data;
|
||||
}
|
||||
|
||||
$offset = @ftell( $this->file_handle );
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total files count in an archive
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_files_count() {
|
||||
if ( is_null( $this->total_files_count ) ) {
|
||||
$this->set_files_totals();
|
||||
}
|
||||
|
||||
return $this->total_files_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total files size in an archive
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_total_files_size() {
|
||||
if ( is_null( $this->total_files_size ) ) {
|
||||
$this->set_files_totals();
|
||||
}
|
||||
|
||||
return $this->total_files_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the total files count and size in the archive
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function set_files_totals() {
|
||||
// Total files count
|
||||
$this->total_files_count = 0;
|
||||
|
||||
// Total files size
|
||||
$this->total_files_size = 0;
|
||||
|
||||
// Seek to beginning of archive file
|
||||
if ( @fseek( $this->file_handle, 0, SEEK_SET ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to beginning of file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Loop over files
|
||||
while ( ( $block = @fread( $this->file_handle, static::HEADER_SIZE ) ) ) {
|
||||
|
||||
// End block has been reached
|
||||
if ( $this->is_eof_block( $block ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get file data from the block
|
||||
if ( ( $data = $this->get_data_from_block( $block ) ) ) {
|
||||
|
||||
// We have a file, increment the count
|
||||
$this->total_files_count += 1;
|
||||
|
||||
// We have a file, increment the size
|
||||
$this->total_files_size += $data['size'];
|
||||
|
||||
// Skip file content so we can move forward to the next file
|
||||
if ( @fseek( $this->file_handle, $data['size'], SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $data['size'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract one file to location
|
||||
*
|
||||
* @param string $location Destination path
|
||||
* @param array $exclude_files Exclude files by name
|
||||
* @param array $exclude_extensions Exclude files by extension
|
||||
* @param array $old_paths Old replace paths
|
||||
* @param array $new_paths New replace paths
|
||||
* @param int $file_bytes_read Amount of the bytes we read
|
||||
* @param int $file_bytes_offset File bytes offset
|
||||
* @param int $file_bytes_written Amount of the bytes we wrote
|
||||
*
|
||||
* @throws \Ai1wm_Not_Directory_Exception
|
||||
* @throws \Ai1wm_Not_Seekable_Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function extract_one_file_to( $location, $exclude_files = array(), $exclude_extensions = array(), $old_paths = array(), $new_paths = array(), &$file_bytes_read = 0, &$file_bytes_offset = 0, &$file_bytes_written = 0 ) {
|
||||
if ( false === is_dir( $location ) ) {
|
||||
throw new Ai1wm_Not_Directory_Exception( sprintf( __( 'Location is not a directory: %s', 'all-in-one-wp-migration' ), $location ) );
|
||||
}
|
||||
|
||||
// Replace forward slash with current directory separator in location
|
||||
$location = ai1wm_replace_forward_slash_with_directory_separator( $location );
|
||||
|
||||
// Flag to hold if file data has been processed
|
||||
$completed = true;
|
||||
|
||||
// Seek to file offset to archive file
|
||||
if ( $file_bytes_offset > 0 ) {
|
||||
if ( @fseek( $this->file_handle, - $file_bytes_offset - static::HEADER_SIZE, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, - $file_bytes_offset - static::HEADER_SIZE ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Read file header block
|
||||
if ( ( $block = @fread( $this->file_handle, static::HEADER_SIZE ) ) ) {
|
||||
|
||||
// We reached end of file, set the pointer to the end of the file so that feof returns true
|
||||
if ( $this->is_eof_block( $block ) ) {
|
||||
|
||||
// Seek to end of archive file minus 1 byte
|
||||
@fseek( $this->file_handle, 1, SEEK_END );
|
||||
|
||||
// Read 1 character
|
||||
@fgetc( $this->file_handle );
|
||||
|
||||
} else {
|
||||
|
||||
// Get file header data from the block
|
||||
if ( ( $data = $this->get_data_from_block( $block ) ) ) {
|
||||
|
||||
// Set file name
|
||||
$file_name = $data['filename'];
|
||||
|
||||
// Set file size
|
||||
$file_size = $data['size'];
|
||||
|
||||
// Set file mtime
|
||||
$file_mtime = $data['mtime'];
|
||||
|
||||
// Set file path
|
||||
$file_path = $data['path'];
|
||||
|
||||
// Set file crc
|
||||
$file_crc32 = $data['crc32'];
|
||||
|
||||
// Should we skip this file by name?
|
||||
$should_exclude_file = false;
|
||||
for ( $i = 0; $i < count( $exclude_files ); $i++ ) {
|
||||
if ( strpos( $file_name . DIRECTORY_SEPARATOR, ai1wm_replace_forward_slash_with_directory_separator( $exclude_files[ $i ] ) . DIRECTORY_SEPARATOR ) === 0 ) {
|
||||
$should_exclude_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should we skip this file by extension?
|
||||
for ( $i = 0; $i < count( $exclude_extensions ); $i++ ) {
|
||||
if ( strrpos( $file_name, $exclude_extensions[ $i ] ) === strlen( $file_name ) - strlen( $exclude_extensions[ $i ] ) ) {
|
||||
$should_exclude_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate file name and file path for directory traversal
|
||||
if ( path_is_absolute( $file_name ) || validate_file( $file_name ) !== 0 ) {
|
||||
$should_exclude_file = true;
|
||||
}
|
||||
|
||||
// Do we have a match?
|
||||
if ( $should_exclude_file === false ) {
|
||||
|
||||
// Replace extract paths
|
||||
for ( $i = 0; $i < count( $old_paths ); $i++ ) {
|
||||
if ( strpos( $file_path . DIRECTORY_SEPARATOR, ai1wm_replace_forward_slash_with_directory_separator( $old_paths[ $i ] ) . DIRECTORY_SEPARATOR ) === 0 ) {
|
||||
$file_name = substr_replace( $file_name, ai1wm_replace_forward_slash_with_directory_separator( $new_paths[ $i ] ), 0, strlen( ai1wm_replace_forward_slash_with_directory_separator( $old_paths[ $i ] ) ) );
|
||||
$file_path = substr_replace( $file_path, ai1wm_replace_forward_slash_with_directory_separator( $new_paths[ $i ] ), 0, strlen( ai1wm_replace_forward_slash_with_directory_separator( $old_paths[ $i ] ) ) );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape Windows directory separator in file path
|
||||
if ( path_is_absolute( $file_path ) ) {
|
||||
$location_file_path = ai1wm_escape_windows_directory_separator( $file_path );
|
||||
} else {
|
||||
$location_file_path = ai1wm_escape_windows_directory_separator( $location . DIRECTORY_SEPARATOR . $file_path );
|
||||
}
|
||||
|
||||
// Escape Windows directory separator in file name
|
||||
if ( path_is_absolute( $file_name ) ) {
|
||||
$location_file_name = ai1wm_escape_windows_directory_separator( $file_name );
|
||||
} else {
|
||||
$location_file_name = ai1wm_escape_windows_directory_separator( $location . DIRECTORY_SEPARATOR . $file_name );
|
||||
}
|
||||
|
||||
// Check if location doesn't exist, then create it
|
||||
if ( false === is_dir( $location_file_path ) ) {
|
||||
@mkdir( $location_file_path, $this->get_permissions_for_directory(), true );
|
||||
}
|
||||
|
||||
$file_bytes_read = 0;
|
||||
|
||||
// We have a match, let's extract the file
|
||||
if ( ( $completed = $this->extract_to( $location_file_name, $file_name, $file_size, $file_mtime, $file_bytes_read, $file_bytes_offset, $file_bytes_written ) ) ) {
|
||||
$file_bytes_offset = $file_bytes_written = 0;
|
||||
|
||||
// Verify CRC32 if present (not empty means version 2 archive with CRC32)
|
||||
if ( ! empty( $file_crc32 ) ) {
|
||||
do_action( 'ai1wm_check_file_integrity', $file_name, $file_crc32 );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// We don't have a match, skip file content
|
||||
if ( @fseek( $this->file_handle, $file_size, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $file_size ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract specific files from archive
|
||||
*
|
||||
* @param string $location Location where to extract files
|
||||
* @param array $include_files Include files by name
|
||||
* @param array $exclude_files Exclude files by name
|
||||
* @param array $exclude_extensions Exclude files by extension
|
||||
* @param int $file_bytes_read Amount of the bytes we read
|
||||
* @param int $file_bytes_offset File bytes offset
|
||||
* @param int $file_bytes_written Amount of the bytes we wrote
|
||||
*
|
||||
* @throws \Ai1wm_Not_Directory_Exception
|
||||
* @throws \Ai1wm_Not_Seekable_Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function extract_by_files_array( $location, $include_files = array(), $exclude_files = array(), $exclude_extensions = array(), &$file_bytes_read = 0, &$file_bytes_offset = 0, &$file_bytes_written = 0 ) {
|
||||
if ( false === is_dir( $location ) ) {
|
||||
throw new Ai1wm_Not_Directory_Exception( sprintf( __( 'Location is not a directory: %s', 'all-in-one-wp-migration' ), $location ) );
|
||||
}
|
||||
|
||||
// Replace forward slash with current directory separator in location
|
||||
$location = ai1wm_replace_forward_slash_with_directory_separator( $location );
|
||||
|
||||
// Flag to hold if file data has been processed
|
||||
$completed = true;
|
||||
|
||||
// Start time
|
||||
$start = microtime( true );
|
||||
|
||||
// Seek to file offset to archive file
|
||||
if ( $file_bytes_offset > 0 ) {
|
||||
if ( @fseek( $this->file_handle, - $file_bytes_offset - static::HEADER_SIZE, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, - $file_bytes_offset - static::HEADER_SIZE ) );
|
||||
}
|
||||
}
|
||||
|
||||
// We read until we reached the end of the file, or the files we were looking for were found
|
||||
while ( ( $block = @fread( $this->file_handle, static::HEADER_SIZE ) ) ) {
|
||||
|
||||
// We reached end of file, set the pointer to the end of the file so that feof returns true
|
||||
if ( $this->is_eof_block( $block ) ) {
|
||||
|
||||
// Seek to end of archive file minus 1 byte
|
||||
@fseek( $this->file_handle, 1, SEEK_END );
|
||||
|
||||
// Read 1 character
|
||||
@fgetc( $this->file_handle );
|
||||
|
||||
} else {
|
||||
|
||||
// Get file header data from the block
|
||||
if ( ( $data = $this->get_data_from_block( $block ) ) ) {
|
||||
|
||||
// Set file name
|
||||
$file_name = $data['filename'];
|
||||
|
||||
// Set file size
|
||||
$file_size = $data['size'];
|
||||
|
||||
// Set file mtime
|
||||
$file_mtime = $data['mtime'];
|
||||
|
||||
// Set file path
|
||||
$file_path = $data['path'];
|
||||
|
||||
// Set file crc
|
||||
$file_crc32 = $data['crc32'];
|
||||
|
||||
// Should we extract this file by name?
|
||||
$should_include_file = false;
|
||||
for ( $i = 0; $i < count( $include_files ); $i++ ) {
|
||||
if ( strpos( $file_name . DIRECTORY_SEPARATOR, ai1wm_replace_forward_slash_with_directory_separator( $include_files[ $i ] ) . DIRECTORY_SEPARATOR ) === 0 ) {
|
||||
$should_include_file = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should we skip this file name?
|
||||
for ( $i = 0; $i < count( $exclude_files ); $i++ ) {
|
||||
if ( strpos( $file_name . DIRECTORY_SEPARATOR, ai1wm_replace_forward_slash_with_directory_separator( $exclude_files[ $i ] ) . DIRECTORY_SEPARATOR ) === 0 ) {
|
||||
$should_include_file = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Should we skip this file by extension?
|
||||
for ( $i = 0; $i < count( $exclude_extensions ); $i++ ) {
|
||||
if ( strrpos( $file_name, $exclude_extensions[ $i ] ) === strlen( $file_name ) - strlen( $exclude_extensions[ $i ] ) ) {
|
||||
$should_include_file = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate file name and file path for directory traversal
|
||||
if ( path_is_absolute( $file_name ) || validate_file( $file_name ) !== 0 ) {
|
||||
$should_include_file = false;
|
||||
}
|
||||
|
||||
// Do we have a match?
|
||||
if ( $should_include_file === true ) {
|
||||
|
||||
// Escape Windows directory separator in file path
|
||||
$location_file_path = ai1wm_escape_windows_directory_separator( $location . DIRECTORY_SEPARATOR . $file_path );
|
||||
|
||||
// Escape Windows directory separator in file name
|
||||
$location_file_name = ai1wm_escape_windows_directory_separator( $location . DIRECTORY_SEPARATOR . $file_name );
|
||||
|
||||
// Check if location doesn't exist, then create it
|
||||
if ( false === is_dir( $location_file_path ) ) {
|
||||
@mkdir( $location_file_path, $this->get_permissions_for_directory(), true );
|
||||
}
|
||||
|
||||
$file_bytes_read = 0;
|
||||
|
||||
// We have a match, let's extract the file
|
||||
if ( ( $completed = $this->extract_to( $location_file_name, $file_name, $file_size, $file_mtime, $file_bytes_read, $file_bytes_offset, $file_bytes_written ) ) ) {
|
||||
$file_bytes_offset = $file_bytes_written = 0;
|
||||
|
||||
// Verify CRC32 if present (not empty means version 2 archive with CRC32)
|
||||
if ( ! empty( $file_crc32 ) ) {
|
||||
do_action( 'ai1wm_check_file_integrity', $file_name, $file_crc32 );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// We don't have a match, skip file content
|
||||
if ( @fseek( $this->file_handle, $file_size, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $file_size ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Time elapsed
|
||||
if ( ( $timeout = apply_filters( 'ai1wm_completed_timeout', 10 ) ) ) {
|
||||
if ( ( microtime( true ) - $start ) > $timeout ) {
|
||||
$completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract file to
|
||||
*
|
||||
* @param string $location_file_name Location file name
|
||||
* @param string $file_name File name
|
||||
* @param array $file_size File size (in bytes)
|
||||
* @param array $file_mtime File modified time (in seconds)
|
||||
* @param int $file_bytes_read Amount of the bytes we read
|
||||
* @param int $file_bytes_offset File bytes offset
|
||||
* @param int $file_bytes_written Amount of the bytes we wrote
|
||||
*
|
||||
* @throws \Ai1wm_Not_Seekable_Exception
|
||||
* @throws \Ai1wm_Not_Readable_Exception
|
||||
* @throws \Ai1wm_Quota_Exceeded_Exception
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function extract_to( $location_file_name, $file_name, $file_size, $file_mtime, &$file_bytes_read = 0, &$file_bytes_offset = 0, &$file_bytes_written = 0 ) {
|
||||
// Flag to hold if file data has been processed
|
||||
$completed = true;
|
||||
|
||||
// Start time
|
||||
$start = microtime( true );
|
||||
|
||||
// Seek to file offset to archive file
|
||||
if ( $file_bytes_offset > 0 ) {
|
||||
if ( @fseek( $this->file_handle, $file_bytes_offset, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $file_size ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Set file size
|
||||
$file_size -= $file_bytes_offset;
|
||||
|
||||
// Should the extract overwrite the file if it exists? (fopen may return null for quarantined files)
|
||||
if ( ( $file_handle = @fopen( $location_file_name, ( $file_bytes_offset === 0 ? 'wb' : 'cb' ) ) ) ) {
|
||||
|
||||
// Set file offset
|
||||
if ( @fseek( $file_handle, $file_bytes_written, SEEK_SET ) !== -1 ) {
|
||||
$file_bytes_read = 0;
|
||||
|
||||
// Cache config file check outside the loop
|
||||
$should_process_file = ! in_array( $file_name, ai1wm_config_filters() );
|
||||
|
||||
// Is the filesize more than 0 bytes?
|
||||
while ( $file_size > 0 ) {
|
||||
|
||||
// Read the file in chunks of 512KB
|
||||
$chunk_size = min( $file_size, static::READ_CHUNK_SIZE );
|
||||
|
||||
// Do not decrypt or decompress config files
|
||||
if ( $should_process_file === true ) {
|
||||
|
||||
// Get decryption chunk size
|
||||
if ( ! empty( $this->file_password ) ) {
|
||||
if ( $file_size > static::READ_CHUNK_SIZE ) {
|
||||
$chunk_size += ai1wm_crypt_iv_length() * 2;
|
||||
$chunk_size = min( $chunk_size, $file_size );
|
||||
}
|
||||
}
|
||||
|
||||
// Read chunk header data
|
||||
if ( ! empty( $this->file_compression ) ) {
|
||||
$chunk_header_size = 4;
|
||||
|
||||
// Get chunk header block
|
||||
if ( ( $chunk_header_block = fread( $this->file_handle, $chunk_header_size ) ) === false ) {
|
||||
throw new Ai1wm_Not_Readable_Exception( sprintf( __( 'Could not read content from file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Get chunk header data
|
||||
if ( ( $chunk_header_data = unpack( 'Nsize', $chunk_header_block ) ) ) {
|
||||
if ( isset( $chunk_header_data['size'] ) ) {
|
||||
$chunk_size = $chunk_header_data['size'];
|
||||
}
|
||||
}
|
||||
|
||||
// Add the amount of bytes we read
|
||||
$file_bytes_read += $chunk_header_size;
|
||||
|
||||
// Remove the amout of bytes we read
|
||||
$file_size -= $chunk_header_size;
|
||||
}
|
||||
}
|
||||
|
||||
// Read data chunk by chunk from archive file
|
||||
if ( $chunk_size > 0 ) {
|
||||
|
||||
// Read the file in chunks of 512KB from archiver
|
||||
if ( ( $file_content = @fread( $this->file_handle, $chunk_size ) ) === false ) {
|
||||
throw new Ai1wm_Not_Readable_Exception( sprintf( __( 'Could not read content from file. File: %s', 'all-in-one-wp-migration' ), $this->file_name ) );
|
||||
}
|
||||
|
||||
// Add the amount of bytes we read
|
||||
$file_bytes_read += $chunk_size;
|
||||
|
||||
// Remove the amout of bytes we read
|
||||
$file_size -= $chunk_size;
|
||||
|
||||
// Do not decrypt or decompress config files
|
||||
if ( $should_process_file === true ) {
|
||||
|
||||
// Add chunk data decryption
|
||||
if ( ! empty( $this->file_password ) ) {
|
||||
$file_content = ai1wm_decrypt_string( $file_content, $this->file_password );
|
||||
}
|
||||
|
||||
// Add chunk data decompression
|
||||
if ( ! empty( $this->file_compression ) ) {
|
||||
switch ( $this->file_compression ) {
|
||||
case 'gzip':
|
||||
$file_content = gzuncompress( $file_content );
|
||||
break;
|
||||
|
||||
case 'bzip2':
|
||||
$file_content = bzdecompress( $file_content );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write file contents
|
||||
if ( ( $file_bytes = @fwrite( $file_handle, $file_content ) ) !== false ) {
|
||||
if ( strlen( $file_content ) !== $file_bytes ) {
|
||||
throw new Ai1wm_Quota_Exceeded_Exception( sprintf( __( 'Out of disk space. Could not write content to file. File: %s', 'all-in-one-wp-migration' ), $location_file_name ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Add the amount of bytes we wrote
|
||||
$file_bytes_written += $file_bytes;
|
||||
}
|
||||
|
||||
// Time elapsed
|
||||
if ( ( $timeout = apply_filters( 'ai1wm_completed_timeout', 10 ) ) ) {
|
||||
if ( ( microtime( true ) - $start ) > $timeout ) {
|
||||
$completed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the amount of bytes we read
|
||||
$file_bytes_offset += $file_bytes_read;
|
||||
}
|
||||
|
||||
// Close the handle
|
||||
@fclose( $file_handle );
|
||||
|
||||
// Let's apply last modified date
|
||||
@touch( $location_file_name, $file_mtime );
|
||||
|
||||
// All files should chmoded to 644
|
||||
@chmod( $location_file_name, $this->get_permissions_for_file() );
|
||||
|
||||
} else {
|
||||
|
||||
// We don't have file permissions, skip file content
|
||||
if ( @fseek( $this->file_handle, $file_size, SEEK_CUR ) === -1 ) {
|
||||
throw new Ai1wm_Not_Seekable_Exception( sprintf( __( 'Could not seek to offset of file. File: %s Offset: %d', 'all-in-one-wp-migration' ), $this->file_name, $file_size ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $completed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file header data from the block
|
||||
*
|
||||
* @param string $block Binary file header
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_data_from_block( $block ) {
|
||||
$data = false;
|
||||
|
||||
// Prepare our array keys to unpack
|
||||
$format = array(
|
||||
$this->block_format[0] . 'filename/',
|
||||
$this->block_format[1] . 'size/',
|
||||
$this->block_format[2] . 'mtime/',
|
||||
$this->block_format[3] . 'path/',
|
||||
$this->block_format[4] . 'crc32',
|
||||
);
|
||||
$format = implode( '', $format );
|
||||
|
||||
// Unpack file header data
|
||||
if ( ( $data = unpack( $format, $block ) ) ) {
|
||||
|
||||
// Set file details
|
||||
$data['filename'] = trim( $data['filename'] );
|
||||
$data['size'] = (int) trim( $data['size'] );
|
||||
$data['mtime'] = (int) trim( $data['mtime'] );
|
||||
$data['path'] = trim( $data['path'] );
|
||||
$data['crc32'] = trim( $data['crc32'] );
|
||||
|
||||
// Set file name
|
||||
$data['filename'] = ( $data['path'] === '.' ? $data['filename'] : $data['path'] . DIRECTORY_SEPARATOR . $data['filename'] );
|
||||
|
||||
// Set file path
|
||||
$data['path'] = ( $data['path'] === '.' ? '' : $data['path'] );
|
||||
|
||||
// Replace forward slash with current directory separator in file name
|
||||
$data['filename'] = ai1wm_replace_forward_slash_with_directory_separator( $data['filename'] );
|
||||
|
||||
// Replace forward slash with current directory separator in file path
|
||||
$data['path'] = ai1wm_replace_forward_slash_with_directory_separator( $data['path'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file has reached end of file
|
||||
* Returns true if file has reached eof, false otherwise
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_reached_eof() {
|
||||
return @feof( $this->file_handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file has reached end of file
|
||||
* Returns true if file has NOT reached eof, false otherwise
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has_not_reached_eof() {
|
||||
return ! @feof( $this->file_handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get directory permissions
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_permissions_for_directory() {
|
||||
if ( defined( 'FS_CHMOD_DIR' ) ) {
|
||||
return FS_CHMOD_DIR;
|
||||
}
|
||||
|
||||
return 0755;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file permissions
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_permissions_for_file() {
|
||||
if ( defined( 'FS_CHMOD_FILE' ) ) {
|
||||
return FS_CHMOD_FILE;
|
||||
}
|
||||
|
||||
return 0644;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user