403 ) ); } /** * Stream a completed local artifact. */ public function handle(): void { if ( ! current_user_can( 'manage_brverify' ) ) { wp_die( esc_html__( 'You do not have permission to download 007AJ Backup Restore Verifier backups.', '007aj-backup-restore-verifier' ), esc_html__( 'Forbidden', '007aj-backup-restore-verifier' ), array( 'response' => 403 ) ); } $artifact_id = isset( $_GET['artifact_id'] ) ? absint( wp_unslash( $_GET['artifact_id'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- checked below. $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : ''; if ( $artifact_id <= 0 || ! wp_verify_nonce( $nonce, 'brverify_download_' . $artifact_id ) ) { wp_die( esc_html__( 'Invalid download request.', '007aj-backup-restore-verifier' ), esc_html__( 'Forbidden', '007aj-backup-restore-verifier' ), array( 'response' => 403 ) ); } $repo = new Job_Repository(); $row = $repo->get_artifact( $artifact_id ); if ( null === $row ) { wp_die( esc_html__( 'Artifact not found.', '007aj-backup-restore-verifier' ), '', array( 'response' => 404 ) ); } $path = (string) ( $row['path'] ?? '' ); if ( ! $this->path_is_allowed( $path ) || ! is_readable( $path ) ) { wp_die( esc_html__( 'Artifact file missing or not allowed.', '007aj-backup-restore-verifier' ), '', array( 'response' => 404 ) ); } $real = realpath( $path ); if ( false === $real || ! is_file( $real ) ) { wp_die( esc_html__( 'Artifact file missing or not allowed.', '007aj-backup-restore-verifier' ), '', array( 'response' => 404 ) ); } $filename = sanitize_file_name( basename( $real ) ); if ( '' === $filename ) { $filename = 'brverify-backup.zip'; } $filesize = filesize( $real ); $filesize = false === $filesize ? 0 : (int) $filesize; nocache_headers(); header( 'Content-Type: application/zip' ); header( 'Content-Disposition: attachment; filename="' . $filename . '"' ); header( 'Content-Length: ' . $filesize ); header( 'X-Content-Type-Options: nosniff' ); header( 'Content-Transfer-Encoding: binary' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile readfile( $real ); exit; } /** * Build a download URL for an artifact. * * @param int $artifact_id Artifact id. */ public static function url( int $artifact_id ): string { return wp_nonce_url( admin_url( 'admin-post.php?action=brverify_download&artifact_id=' . absint( $artifact_id ) ), 'brverify_download_' . absint( $artifact_id ) ); } /** * Ensure path is under working storage (realpath + Path_Guard). * * @param string $path Absolute path. */ private function path_is_allowed( string $path ): bool { if ( '' === $path ) { return false; } $storage = realpath( Working_Dir::storage() ); $real = realpath( $path ); if ( false === $storage || false === $real ) { return false; } if ( ! is_file( $real ) ) { return false; } return Path_Guard::is_under( $real, $storage ); } }