working_dir(); return is_dir( $dir ) && is_writable( $dir ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable } /** * {@inheritdoc} */ public function push_chunk( string $local_path, array $cursor, array $context ): array { if ( ! is_readable( $local_path ) ) { return array( 'ok' => false, 'error' => __( 'Local source path is not readable.', '007aj-backup-restore-verifier' ), ); } if ( ! Working_Dir::ensure() ) { return array( 'ok' => false, 'error' => __( 'Unable to prepare local storage directory.', '007aj-backup-restore-verifier' ), ); } $storage = Working_Dir::storage(); $name = isset( $context['zip_name'] ) ? (string) $context['zip_name'] : basename( $local_path ); $name = sanitize_file_name( $name ); if ( '' === $name ) { $name = 'brverify-backup.zip'; } $dest = trailingslashit( $storage ) . $name; // Avoid clobber: uniquify if needed. if ( file_exists( $dest ) && realpath( $local_path ) !== realpath( $dest ) ) { $dest = trailingslashit( $storage ) . wp_unique_filename( $storage, $name ); } $same = ( realpath( $local_path ) && realpath( dirname( $dest ) ) ) && realpath( dirname( $local_path ) ) === realpath( dirname( $dest ) ) && basename( $local_path ) === basename( $dest ); if ( ! $same ) { // Prefer rename (same filesystem); fall back to copy. // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename $moved = @rename( $local_path, $dest ); if ( ! $moved ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_copy $copied = @copy( $local_path, $dest ); if ( ! $copied ) { return array( 'ok' => false, 'error' => __( 'Failed to move backup into local storage.', '007aj-backup-restore-verifier' ), ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink @unlink( $local_path ); } } else { $dest = $local_path; } $bytes = filesize( $dest ); $bytes = false === $bytes ? 0 : (int) $bytes; $ref = 'local://storage/' . basename( $dest ); $cursor['ref'] = $ref; $cursor['stored_path'] = $dest; return array( 'ok' => true, 'bytes' => $bytes, 'ref' => $ref, 'stored_path' => $dest, 'cursor' => $cursor, ); } /** * {@inheritdoc} */ public function finalize( array $cursor, array $context ): array { unset( $context ); $ref = isset( $cursor['ref'] ) ? (string) $cursor['ref'] : ''; $path = isset( $cursor['stored_path'] ) ? (string) $cursor['stored_path'] : ''; if ( '' === $path || ! is_readable( $path ) ) { return array( 'ok' => false, 'error' => __( 'Finalized local artifact is missing.', '007aj-backup-restore-verifier' ), ); } return array( 'ok' => true, 'ref' => $ref ? $ref : 'local://storage/' . basename( $path ), ); } /** * {@inheritdoc} */ public function list_remote( array $args ): array { unset( $args ); $dir = Working_Dir::storage(); if ( ! is_dir( $dir ) ) { return array(); } $files = scandir( $dir ); if ( false === $files ) { return array(); } $out = array(); foreach ( $files as $file ) { if ( '.' === $file || '..' === $file ) { continue; } if ( ! \brverify_str_ends_with( strtolower( $file ), '.zip' ) ) { continue; } $path = trailingslashit( $dir ) . $file; if ( ! is_file( $path ) ) { continue; } $size = filesize( $path ); $out[] = array( 'name' => $file, 'path' => $path, 'bytes' => false === $size ? 0 : (int) $size, 'ref' => 'local://storage/' . $file, ); } return $out; } /** * {@inheritdoc} */ public function pull_chunk( string $ref, string $dest, array $cursor ): array { $path = $this->ref_to_path( $ref ); if ( null === $path || ! is_readable( $path ) ) { return array( 'ok' => false, 'error' => __( 'Local artifact not found.', '007aj-backup-restore-verifier' ), ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_copy if ( ! @copy( $path, $dest ) ) { return array( 'ok' => false, 'error' => __( 'Failed to copy local artifact.', '007aj-backup-restore-verifier' ), ); } $bytes = filesize( $dest ); return array( 'ok' => true, 'bytes' => false === $bytes ? 0 : (int) $bytes, 'cursor' => $cursor, ); } /** * {@inheritdoc} */ public function delete( string $ref ): bool { $path = $this->ref_to_path( $ref ); if ( null === $path || ! file_exists( $path ) ) { return false; } // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink return @unlink( $path ); } /** * Absolute working directory path (root). */ public function working_dir(): string { return Working_Dir::root(); } /** * Map local://storage/name to absolute path under storage (hardened). * * @param string $ref Reference. */ private function ref_to_path( string $ref ): ?string { if ( ! \brverify_str_starts_with( $ref, 'local://storage/' ) ) { return null; } $name = substr( $ref, strlen( 'local://storage/' ) ); $name = basename( $name ); if ( '' === $name || \brverify_str_contains( $name, '..' ) ) { return null; } $storage = realpath( Working_Dir::storage() ); if ( false === $storage ) { return null; } $path = $storage . DIRECTORY_SEPARATOR . $name; $real = realpath( $path ); if ( false === $real || ! \brverify_str_starts_with( $real, $storage ) ) { return null; } return $real; } }