knops / virustotal-client
A PHP client for the VirusTotal API v3
Requires
- php: ^8.1
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.0 || ^2.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.87
- guzzlehttp/guzzle: ^7.0
- nyholm/psr7: ^1.0
- phpunit/phpunit: ^10.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A PHP library for scanning files using the VirusTotal API v3. This library follows PSR standards and provides a clean, type-safe interface for file scanning and analysis.
Features
- ✅ PSR-18 HTTP Client - Uses PSR HTTP standards for maximum compatibility
- ✅ Type Safety - Full PHP 8.1+ type declarations with strict types
- ✅ Comprehensive API - Upload files, check analysis status, and get detailed results
- ✅ Error Handling - Proper exception handling with descriptive messages
- ✅ Async Support - Poll for results or wait for completion
- ✅ Protected ZIP Support - Handle password-protected ZIP files
- ✅ Detailed Results - Access engine-specific results and statistics
- ✅ Factory Pattern - Easy setup with popular HTTP clients
Installation
Install via Composer:
composer require knops/virustotal-client
For the easiest setup with Guzzle HTTP client:
composer require knops/virustotal-client guzzlehttp/guzzle nyholm/psr7
Quick Start
Using the Factory (Recommended)
<?php use Knops\VirustotalClient\VirusTotalClientFactory; // Create client with Guzzle (easiest) $client = VirusTotalClientFactory::createWithGuzzle('your-api-key-here'); // Scan a file and wait for results $result = $client->scanFileAndWait('/path/to/file.pdf'); // Check if file is malicious if ($result->isMalicious()) { echo "⚠️ File is malicious! Detected by {$result->getMaliciousCount()} engines\n"; // Get details about malicious detections foreach ($result->getMaliciousResults() as $engine => $detection) { echo "- {$engine}: {$detection['result']}\n"; } } else { echo "✅ File is clean!\n"; }
Manual Setup with Custom HTTP Client
<?php use Knops\VirustotalClient\VirusTotalClient; use GuzzleHttp\Client; use Nyholm\Psr7\Factory\Psr17Factory; $httpClient = new Client(); $psr17Factory = new Psr17Factory(); $client = new VirusTotalClient( 'your-api-key-here', $httpClient, $psr17Factory, // Request factory $psr17Factory // Stream factory );
Usage Examples
Basic File Scanning
// Upload file for scanning $scanResponse = $client->scanFile('/path/to/suspicious-file.exe'); $analysisId = $scanResponse->getAnalysisId(); // Check analysis status $analysis = $client->getAnalysis($analysisId); if ($analysis->isCompleted()) { echo "Scan completed!\n"; echo "Status: " . ($analysis->isClean() ? 'Clean' : 'Threat detected') . "\n"; } else { echo "Scan still in progress...\n"; }
Scanning Password-Protected ZIP Files
$result = $client->scanFile('/path/to/protected.zip', 'password123');
Getting Detailed Results
$analysis = $client->getAnalysis($analysisId); // Get summary information $summary = $analysis->getSummary(); print_r($summary); // Get statistics $stats = $analysis->getStats(); echo "Malicious: {$stats['malicious']}\n"; echo "Suspicious: {$stats['suspicious']}\n"; echo "Clean: {$stats['harmless']}\n"; echo "Undetected: {$stats['undetected']}\n"; // Get file information echo "File: {$analysis->getFileName()}\n"; echo "Size: {$analysis->getFileSize()} bytes\n"; echo "Type: {$analysis->getFileType()}\n"; echo "SHA256: {$analysis->getFileHash()}\n";
Advanced: Custom Polling
$scanResponse = $client->scanFile('/path/to/file.pdf'); $analysisId = $scanResponse->getAnalysisId(); // Poll every 5 seconds for up to 2 minutes $maxWait = 120; // seconds $pollInterval = 5; // seconds $startTime = time(); while (time() - $startTime < $maxWait) { $analysis = $client->getAnalysis($analysisId); if ($analysis->isCompleted()) { echo "Analysis completed!\n"; break; } echo "Still scanning... ({$analysis->getStatus()})\n"; sleep($pollInterval); }
API Reference
VirusTotalClient
Methods
-
scanFile(string $filePath, ?string $password = null): ScanResponse- Upload a file for scanning
- Returns scan response with analysis ID
-
getAnalysis(string $analysisId): AnalysisResponse- Get analysis results by ID
- Returns detailed analysis response
-
scanFileAndWait(string $filePath, ?string $password = null, int $maxWaitTime = 300, int $pollInterval = 10): AnalysisResponse- Scan file and wait for completion
- Automatically polls for results
ScanResponse
Methods
getAnalysisId(): string- Get the analysis ID for checking resultsgetType(): string- Get the response typegetRawData(): array- Get raw API response data
AnalysisResponse
Status Methods
isCompleted(): bool- Check if analysis is finishedgetStatus(): string- Get current analysis statusisMalicious(): bool- Check if file has malicious detectionsisSuspicious(): bool- Check if file has suspicious detectionsisClean(): bool- Check if file is clean
Statistics Methods
getStats(): array- Get detection statisticsgetMaliciousCount(): int- Number of malicious detectionsgetSuspiciousCount(): int- Number of suspicious detectionsgetTotalEngines(): int- Total number of scanning engines
Results Methods
getEngineResults(): array- Get all engine resultsgetMaliciousResults(): array- Get only malicious detectionsgetSuspiciousResults(): array- Get only suspicious detections
File Information Methods
getFileHash(): ?string- Get SHA256 hashgetFileName(): ?string- Get original filenamegetFileSize(): ?int- Get file size in bytesgetFileType(): ?string- Get file type descriptiongetScanDate(): ?int- Get scan timestamp
Utility Methods
getSummary(): array- Get comprehensive summarygetRawData(): array- Get raw API response data
Error Handling
The library throws VirusTotalException for various error conditions:
use Knops\VirustotalClient\Exception\VirusTotalException; try { $result = $client->scanFile('/path/to/file.pdf'); } catch (VirusTotalException $e) { echo "Error: " . $e->getMessage() . "\n"; // Handle specific error cases if (str_contains($e->getMessage(), 'File not found')) { echo "The specified file does not exist.\n"; } elseif (str_contains($e->getMessage(), 'API request failed')) { echo "There was an issue with the VirusTotal API.\n"; } }
Requirements
- PHP 8.1 or higher
- PSR-18 HTTP Client implementation
- PSR-17 HTTP Factory implementation
- VirusTotal API key
Getting a VirusTotal API Key
- Sign up at VirusTotal
- Go to your profile settings
- Copy your API key from the API Key section
Rate Limits
VirusTotal has rate limits based on your account type:
- Public API: 4 requests per minute
- Premium API: Higher limits based on subscription
The library does not automatically handle rate limiting, so you may need to implement delays between requests for high-volume usage.
Testing
Run the test suite:
composer install vendor/bin/phpunit
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
This library is licensed under the MIT License. See the LICENSE file for details.
Security
If you discover any security-related issues, please email ignace@knopsmedia.be instead of using the issue tracker.
Changelog
1.0.0
- Initial release
- File scanning functionality
- Analysis result retrieval
- PSR HTTP client support
- Comprehensive test coverage