plugin_version = $plugin_version; $this->paths = $paths ?? new WordPressPathProvider(); } public function download( string $api_key ): string { if ( strlen( $api_key ) < 16 || strlen( $api_key ) > 512 || 1 !== preg_match( '/^[\x21-\x7e]+$/D', $api_key ) ) { throw new \InvalidArgumentException( 'wordfence_api_key_invalid' ); } $directory = realpath( sys_get_temp_dir() ); if ( false === $directory || ! is_dir( $directory ) || $this->inside_wordpress( $directory ) ) { throw new ExternalRequestException( 'wordfence_private_temp_unavailable' ); } $free = disk_free_space( $directory ); if ( false === $free || $free < self::MAX_BYTES + 67108864 ) { throw new ExternalRequestException( 'wordfence_private_temp_space_low' ); } // Random OS-temp file for a public feed; never a WordPress/public path. // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_tempnam $path = tempnam( $directory, 'px320-wf-' ); if ( false === $path ) { throw new ExternalRequestException( 'wordfence_private_temp_unavailable' ); } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod if ( ! chmod( $path, 0600 ) ) { $this->delete( $path ); throw new ExternalRequestException( 'wordfence_private_temp_unavailable' ); } $response = wp_safe_remote_get( self::ENDPOINT, array( 'timeout' => 120, 'redirection' => 0, 'sslverify' => true, 'stream' => true, 'filename' => $path, 'limit_response_size' => self::MAX_BYTES, 'headers' => array( 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $api_key, 'Cache-Control' => 'no-store', 'User-Agent' => '320px-Site-Audit/' . $this->plugin_version, ), ) ); if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) { $this->delete( $path ); throw new ExternalRequestException( 'wordfence_feed_request_failed' ); } clearstatcache( true, $path ); $size = filesize( $path ); if ( false === $size || $size < 2 || $size > self::MAX_BYTES ) { $this->delete( $path ); throw new ExternalRequestException( 'wordfence_feed_size_invalid' ); } return $path; } public function delete( string $path ): void { $directory = realpath( dirname( $path ) ); if ( false === $directory || realpath( sys_get_temp_dir() ) !== $directory || 0 !== strpos( basename( $path ), 'px320-wf-' ) || ! is_file( $path ) || is_link( $path ) ) { return; } // Deletes only the exact temporary feed file created by this adapter. // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink unlink( $path ); } private function inside_wordpress( string $path ): bool { $path = rtrim( wp_normalize_path( $path ), '/' ) . '/'; foreach ( array( $this->paths->wordpress_root(), $this->paths->content_root() ) as $candidate ) { if ( '' === $candidate ) { continue; } $root = realpath( $candidate ); if ( false === $root || 0 === strpos( $path, rtrim( wp_normalize_path( $root ), '/' ) . '/' ) ) { return true; } } return false; } }