Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
e31e7cca44 | |||
a19638b476 | |||
f71219f0ca | |||
e7e9cb30e7 | |||
dbe0821429 | |||
5500524ce7 |
18
changelog.md
18
changelog.md
@ -1,5 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-02-03 - 1.1.0 - feat(ClamAvService)
|
||||
Add ClamAV Manager with Docker container management capabilities.
|
||||
|
||||
- Introduced ClamAVManager class to manage ClamAV Docker containers.
|
||||
- Implemented startContainer and stopContainer methods in ClamAVManager.
|
||||
- Integrated ClamAVManager into ClamAvService for managing container lifecycle.
|
||||
- Added ClamAVManager test setups and helpers in test suite.
|
||||
|
||||
## 2025-01-10 - 1.0.4 - fix(documentation)
|
||||
Removed redundant conclusion section in readme.
|
||||
|
||||
- Removed the conclusion section from the README file for conciseness.
|
||||
|
||||
## 2025-01-10 - 1.0.3 - fix(readme)
|
||||
Fix formatting errors in the README file for consistent Markdown syntax.
|
||||
|
||||
- Removed stray Markdown syntax in README file.
|
||||
|
||||
## 2025-01-10 - 1.0.2 - fix(documentation)
|
||||
Updated README and package metadata to reflect antivirus scanning capabilities and usage instructions.
|
||||
|
||||
|
19
license
Normal file
19
license
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2025 Task Venture Capital GmbH (hello@task.vc)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartantivirus",
|
||||
"version": "1.0.2",
|
||||
"version": "1.1.0",
|
||||
"private": false,
|
||||
"description": "A Node.js package for integrating antivirus scanning capabilities using ClamAV, allowing in-memory file and data scanning.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
@ -1,4 +1,3 @@
|
||||
```markdown
|
||||
# @push.rocks/smartantivirus
|
||||
|
||||
A package for performing antivirus testing, especially suitable for use with ClamAV.
|
||||
@ -119,9 +118,3 @@ The tests include creating and utilizing a `ClamAvService` instance and attempts
|
||||
Beyond scanning strings and buffers, you can implement additional advanced use cases based on your specific application needs, such as integrating into web services or automating file scans in cloud environments. Consider building upon provided functionalities and adapting them to meet the requirements of your application architecture.
|
||||
|
||||
With the help of Node.js worker threads or external task queues like RabbitMQ, you can distribute scanning tasks efficiently within high-traffic environments.
|
||||
|
||||
### Conclusion
|
||||
|
||||
`@push.rocks/smartantivirus` provides a simple yet effective way to incorporate antivirus checks into your Node.js applications, leveraging the robust ClamAV engine. With features like in-memory scanning and connection verification, you can seamlessly ensure your data security solutions are integrated into your application lifecycle.
|
||||
```
|
||||
undefined
|
90
test/helpers/clamav.helper.ts
Normal file
90
test/helpers/clamav.helper.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { ClamAVManager } from '../../ts/classes.clamav.manager.js';
|
||||
import { execAsync } from '../../ts/plugins.js';
|
||||
|
||||
let clamManager: ClamAVManager | null = null;
|
||||
let isCleaningUp = false;
|
||||
|
||||
export async function getManager(): Promise<ClamAVManager> {
|
||||
if (!clamManager) {
|
||||
throw new Error('ClamAV manager not initialized');
|
||||
}
|
||||
return clamManager;
|
||||
}
|
||||
|
||||
export async function setupClamAV(): Promise<ClamAVManager> {
|
||||
console.log('[Helper] Setting up ClamAV...');
|
||||
|
||||
// First cleanup any existing containers
|
||||
await forceCleanupContainer();
|
||||
|
||||
if (!clamManager) {
|
||||
console.log('[Helper] Creating new ClamAV manager instance');
|
||||
clamManager = new ClamAVManager();
|
||||
await clamManager.startContainer();
|
||||
console.log('[Helper] ClamAV manager initialized');
|
||||
} else {
|
||||
console.log('[Helper] Using existing ClamAV manager instance');
|
||||
}
|
||||
|
||||
return clamManager;
|
||||
}
|
||||
|
||||
export async function cleanupClamAV(): Promise<void> {
|
||||
if (isCleaningUp) {
|
||||
console.log('[Helper] Cleanup already in progress, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
isCleaningUp = true;
|
||||
console.log('[Helper] Cleaning up ClamAV...');
|
||||
|
||||
try {
|
||||
if (clamManager) {
|
||||
await clamManager.stopContainer();
|
||||
console.log('[Helper] ClamAV container stopped');
|
||||
}
|
||||
await forceCleanupContainer();
|
||||
} catch (error) {
|
||||
console.error('[Helper] Error during cleanup:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
clamManager = null;
|
||||
isCleaningUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function forceCleanupContainer(): Promise<void> {
|
||||
try {
|
||||
// Stop any existing container
|
||||
await execAsync('docker stop clamav-daemon').catch(() => {});
|
||||
// Remove any existing container
|
||||
await execAsync('docker rm -f clamav-daemon').catch(() => {});
|
||||
console.log('[Helper] Forced cleanup of existing containers complete');
|
||||
} catch (error) {
|
||||
// Ignore errors as the container might not exist
|
||||
}
|
||||
}
|
||||
|
||||
// Handle interrupts
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\n[Helper] Received SIGINT. Cleaning up...');
|
||||
try {
|
||||
await cleanupClamAV();
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
console.error('[Helper] Error during cleanup:', err);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure cleanup on process exit
|
||||
process.on('exit', () => {
|
||||
if (clamManager && !isCleaningUp) {
|
||||
console.log('[Helper] Process exit detected, attempting cleanup');
|
||||
// We can't use async functions in exit handler, so we do our best
|
||||
try {
|
||||
execAsync('docker stop clamav-daemon').catch(() => {});
|
||||
execAsync('docker rm -f clamav-daemon').catch(() => {});
|
||||
} catch {}
|
||||
}
|
||||
});
|
55
test/test.clamav.manager.ts
Normal file
55
test/test.clamav.manager.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { expect, tap } from '../ts/plugins.js';
|
||||
import { type ClamAVLogEvent, ClamAVManager } from '../ts/classes.clamav.manager.js';
|
||||
import { setupClamAV, cleanupClamAV, getManager } from './helpers/clamav.helper.js';
|
||||
|
||||
let manager: ClamAVManager;
|
||||
|
||||
tap.test('setup', async () => {
|
||||
manager = await setupClamAV();
|
||||
expect(manager).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should have initialized container and receive logs', async () => {
|
||||
let logReceived = false;
|
||||
|
||||
// Add event listener for logs
|
||||
manager.on('log', (event: ClamAVLogEvent) => {
|
||||
console.log(`[Test] Received log event: ${event.type} - ${event.message}`);
|
||||
logReceived = true;
|
||||
});
|
||||
|
||||
// Wait for logs
|
||||
const maxWaitTime = 5000;
|
||||
const startTime = Date.now();
|
||||
|
||||
while (!logReceived && Date.now() - startTime < maxWaitTime) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
expect(logReceived).toBeTruthy('No logs received within timeout period');
|
||||
|
||||
// Verify container is running by checking if we can get database info
|
||||
try {
|
||||
const dbInfo = await manager.getDatabaseInfo();
|
||||
expect(dbInfo).toBeTruthy('Container should be running and able to get database info');
|
||||
} catch (error) {
|
||||
console.error('Error getting database info:', error);
|
||||
expect.fail('Failed to get database info - container may not be fully initialized');
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('should get database info', async () => {
|
||||
const dbInfo = await manager.getDatabaseInfo();
|
||||
console.log('Database Info:', dbInfo);
|
||||
expect(dbInfo).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should update database', async () => {
|
||||
await manager.updateDatabase();
|
||||
});
|
||||
|
||||
tap.test('cleanup', async () => {
|
||||
await cleanupClamAV();
|
||||
});
|
||||
|
||||
tap.start();
|
49
test/test.ts
49
test/test.ts
@ -1,35 +1,40 @@
|
||||
import { expect, expectAsync, tap } from '@push.rocks/tapbundle';
|
||||
import { expect, tap } from '../ts/plugins.js';
|
||||
import * as smartantivirus from '../ts/index.js';
|
||||
import { setupClamAV, cleanupClamAV } from './helpers/clamav.helper.js';
|
||||
|
||||
const EICAR_TEST_STRING = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*';
|
||||
let clamService: smartantivirus.ClamAvService;
|
||||
|
||||
tap.test('should create a ClamAvService instance', async () => {
|
||||
clamService = new smartantivirus.ClamAvService();
|
||||
expect(clamService).toBeDefined();
|
||||
tap.test('setup', async () => {
|
||||
await setupClamAV();
|
||||
});
|
||||
|
||||
tap.test('should scan a string', async () => {
|
||||
const scanResult = await clamService.scanString('X5O!P%@AP[4\PZX54(P^)7CC)7}' + '$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*');
|
||||
tap.test('should create a ClamAvService instance and initialize ClamAV', async () => {
|
||||
clamService = new smartantivirus.ClamAvService();
|
||||
expect(clamService).toBeTruthy();
|
||||
// The manager will start the container and wait for initialization
|
||||
await clamService.verifyConnection();
|
||||
});
|
||||
|
||||
tap.test('should detect EICAR test string', async () => {
|
||||
const scanResult = await clamService.scanString(EICAR_TEST_STRING);
|
||||
console.log('Scan Result:', scanResult);
|
||||
// expect(scanResult).toEqual({ isInfected: true, reason: 'FOUND' });
|
||||
expect(scanResult.isInfected).toEqual(true);
|
||||
expect(scanResult.reason).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('should not detect clean string', async () => {
|
||||
const scanResult = await clamService.scanString('This is a clean string with no virus signature');
|
||||
console.log('Clean Scan Result:', scanResult);
|
||||
expect(scanResult.isInfected).toEqual(false);
|
||||
expect(scanResult.reason).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('cleanup', async () => {
|
||||
await cleanupClamAV();
|
||||
});
|
||||
|
||||
tap.start();
|
||||
|
||||
|
||||
/* (async () => {
|
||||
|
||||
try {
|
||||
|
||||
await clamService.updateVirusDefinitions(); // Step 2: Update definitions
|
||||
await clamService.startClamDaemon(); // Step 3: Start daemon
|
||||
|
||||
const scanResult = await clamService.scanString('EICAR test string...');
|
||||
console.log('Scan Result:', scanResult);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
})(); */
|
||||
|
||||
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartantivirus',
|
||||
version: '1.0.2',
|
||||
version: '1.1.0',
|
||||
description: 'A Node.js package for integrating antivirus scanning capabilities using ClamAV, allowing in-memory file and data scanning.'
|
||||
}
|
||||
|
274
ts/classes.clamav.manager.ts
Normal file
274
ts/classes.clamav.manager.ts
Normal file
@ -0,0 +1,274 @@
|
||||
import { exec, spawn, net, promisify, EventEmitter, execAsync } from './plugins.js';
|
||||
|
||||
export interface ClamAVLogEvent {
|
||||
timestamp: string;
|
||||
message: string;
|
||||
type: 'update' | 'scan' | 'system' | 'error';
|
||||
}
|
||||
|
||||
export class ClamAVManager extends EventEmitter {
|
||||
private containerId: string | null = null;
|
||||
private containerName = 'clamav-daemon';
|
||||
private imageTag = 'clamav/clamav:latest';
|
||||
private port = 3310;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the ClamAV container if it's not already running
|
||||
*/
|
||||
public async startContainer(): Promise<void> {
|
||||
try {
|
||||
console.log('[ClamAV] Starting container initialization...');
|
||||
|
||||
// Check if container is already running
|
||||
const { stdout: psOutput } = await execAsync('docker ps --filter name=' + this.containerName);
|
||||
if (psOutput.includes(this.containerName)) {
|
||||
console.log('[ClamAV] Container is already running');
|
||||
this.containerId = (await execAsync(`docker ps -q --filter name=${this.containerName}`)).stdout.trim();
|
||||
console.log('[ClamAV] Container ID:', this.containerId);
|
||||
this.attachLogWatcher();
|
||||
await this.waitForInitialization();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if container exists but is stopped
|
||||
const { stdout: psaOutput } = await execAsync('docker ps -a --filter name=' + this.containerName);
|
||||
if (psaOutput.includes(this.containerName)) {
|
||||
console.log('[ClamAV] Found stopped container, starting it...');
|
||||
await execAsync(`docker start ${this.containerName}`);
|
||||
this.containerId = (await execAsync(`docker ps -q --filter name=${this.containerName}`)).stdout.trim();
|
||||
console.log('[ClamAV] Started existing container, ID:', this.containerId);
|
||||
} else {
|
||||
// Create and start new container
|
||||
console.log('[ClamAV] Creating new container...');
|
||||
const { stdout } = await execAsync(
|
||||
`docker run -d --name ${this.containerName} -p ${this.port}:3310 ${this.imageTag}`
|
||||
);
|
||||
this.containerId = stdout.trim();
|
||||
console.log('[ClamAV] Created new container, ID:', this.containerId);
|
||||
}
|
||||
|
||||
this.attachLogWatcher();
|
||||
console.log('[ClamAV] Waiting for initialization...');
|
||||
await this.waitForInitialization();
|
||||
console.log('[ClamAV] Container successfully initialized');
|
||||
} catch (error) {
|
||||
console.error('[ClamAV] Error starting container:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the ClamAV container
|
||||
*/
|
||||
public async stopContainer(): Promise<void> {
|
||||
if (!this.containerId) {
|
||||
console.log('No ClamAV container is running');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await execAsync(`docker stop ${this.containerId}`);
|
||||
console.log('Stopped ClamAV container');
|
||||
} catch (error) {
|
||||
console.error('Error stopping ClamAV container:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually trigger a database update
|
||||
*/
|
||||
public async updateDatabase(): Promise<void> {
|
||||
if (!this.containerId) {
|
||||
throw new Error('ClamAV container is not running');
|
||||
}
|
||||
|
||||
try {
|
||||
// First check if freshclam is already running
|
||||
const { stdout: psOutput } = await execAsync(`docker exec ${this.containerId} ps aux | grep freshclam`);
|
||||
if (psOutput.includes('/usr/local/sbin/freshclam -d')) {
|
||||
console.log('Freshclam daemon is already running');
|
||||
// Wait a bit to ensure database is updated
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
return;
|
||||
}
|
||||
|
||||
// If not running as daemon, try to update manually
|
||||
const { stdout, stderr } = await execAsync(`docker exec ${this.containerId} freshclam --no-warnings`);
|
||||
console.log('Database update output:', stdout);
|
||||
if (stderr) {
|
||||
console.error('Database update errors:', stderr);
|
||||
}
|
||||
} catch (error) {
|
||||
// Check if the error is due to freshclam already running
|
||||
if (error.stderr?.includes('ERROR: Problem with internal logger') ||
|
||||
error.stdout?.includes('Resource temporarily unavailable')) {
|
||||
console.log('Freshclam is already running, skipping manual update');
|
||||
return;
|
||||
}
|
||||
console.error('Error updating ClamAV database:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current database version information
|
||||
*/
|
||||
public async getDatabaseInfo(): Promise<string> {
|
||||
if (!this.containerId) {
|
||||
throw new Error('ClamAV container is not running');
|
||||
}
|
||||
|
||||
try {
|
||||
// Try both .cld and .cvd files since ClamAV can use either format
|
||||
try {
|
||||
const { stdout } = await execAsync(`docker exec ${this.containerId} sigtool --info /var/lib/clamav/daily.cld`);
|
||||
return stdout;
|
||||
} catch {
|
||||
const { stdout } = await execAsync(`docker exec ${this.containerId} sigtool --info /var/lib/clamav/daily.cvd`);
|
||||
return stdout;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting database info:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch container logs and emit events for different types of log messages
|
||||
*/
|
||||
private attachLogWatcher(): void {
|
||||
if (!this.containerId) return;
|
||||
|
||||
const logProcess = spawn('docker', ['logs', '-f', this.containerId]);
|
||||
|
||||
logProcess.stdout.on('data', (data) => {
|
||||
const lines = data.toString().split('\n');
|
||||
lines.forEach(line => {
|
||||
if (!line.trim()) return;
|
||||
|
||||
const event: ClamAVLogEvent = {
|
||||
timestamp: new Date().toISOString(),
|
||||
message: line,
|
||||
type: this.determineLogType(line)
|
||||
};
|
||||
|
||||
this.emit('log', event);
|
||||
console.log(`[ClamAV ${event.type}] ${event.message}`);
|
||||
});
|
||||
});
|
||||
|
||||
logProcess.stderr.on('data', (data) => {
|
||||
const event: ClamAVLogEvent = {
|
||||
timestamp: new Date().toISOString(),
|
||||
message: data.toString(),
|
||||
type: 'error'
|
||||
};
|
||||
|
||||
this.emit('log', event);
|
||||
console.error(`[ClamAV error] ${event.message}`);
|
||||
});
|
||||
|
||||
logProcess.on('error', (error) => {
|
||||
console.error('Error in log watcher:', error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the type of log message
|
||||
*/
|
||||
private determineLogType(logMessage: string): ClamAVLogEvent['type'] {
|
||||
const lowerMessage = logMessage.toLowerCase();
|
||||
if (lowerMessage.includes('update') || lowerMessage.includes('freshclam')) {
|
||||
return 'update';
|
||||
} else if (lowerMessage.includes('scan') || lowerMessage.includes('found')) {
|
||||
return 'scan';
|
||||
} else if (lowerMessage.includes('error') || lowerMessage.includes('warning')) {
|
||||
return 'error';
|
||||
}
|
||||
return 'system';
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for ClamAV to initialize by checking both logs and service readiness
|
||||
*/
|
||||
private async waitForInitialization(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.containerId) {
|
||||
reject(new Error('Container ID not set'));
|
||||
return;
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout;
|
||||
let checkCount = 0;
|
||||
const maxChecks = 60; // Check for 60 seconds
|
||||
const startTime = Date.now();
|
||||
|
||||
// Check service readiness
|
||||
const checkService = async () => {
|
||||
try {
|
||||
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
||||
console.log(`[ClamAV] Checking service readiness (attempt ${checkCount + 1}, ${elapsedTime}s elapsed)...`);
|
||||
|
||||
// First check if the service is accepting connections
|
||||
const client = new net.Socket();
|
||||
await new Promise<void>((resolveConn, rejectConn) => {
|
||||
const connectTimeout = setTimeout(() => {
|
||||
client.destroy();
|
||||
rejectConn(new Error('Connection timeout'));
|
||||
}, 1000);
|
||||
|
||||
client.connect(this.port, 'localhost', () => {
|
||||
clearTimeout(connectTimeout);
|
||||
client.end();
|
||||
resolveConn();
|
||||
});
|
||||
|
||||
client.on('error', (err) => {
|
||||
clearTimeout(connectTimeout);
|
||||
rejectConn(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Verify the service is responding to commands
|
||||
const { stdout } = await execAsync(`echo PING | nc localhost ${this.port}`);
|
||||
if (!stdout.includes('PONG')) {
|
||||
throw new Error('Service not responding to commands');
|
||||
}
|
||||
|
||||
// If we can connect and get a PONG, the service is ready
|
||||
console.log('[ClamAV] Service is accepting connections and responding to commands');
|
||||
cleanup();
|
||||
resolve();
|
||||
} catch (error) {
|
||||
// Service not ready yet, will retry
|
||||
if (checkCount >= maxChecks) {
|
||||
cleanup();
|
||||
reject(new Error(`ClamAV initialization timed out after ${maxChecks} seconds. Last error: ${error.message}`));
|
||||
return;
|
||||
}
|
||||
checkCount++;
|
||||
}
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
clearTimeout(timeout);
|
||||
clearInterval(serviceCheck);
|
||||
};
|
||||
|
||||
const serviceCheck = setInterval(checkService, 1000);
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
cleanup();
|
||||
reject(new Error('ClamAV initialization timed out after 60 seconds'));
|
||||
}, 60000);
|
||||
|
||||
// Start initial service check
|
||||
checkService();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,25 +1,35 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as paths from './paths.js';
|
||||
|
||||
import { exec } from 'child_process';
|
||||
import net from 'net';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
import { net } from './plugins.js';
|
||||
import { ClamAVManager } from './classes.clamav.manager.js';
|
||||
|
||||
export class ClamAvService {
|
||||
private host: string;
|
||||
private port: number;
|
||||
private manager: ClamAVManager;
|
||||
|
||||
constructor(host: string = '127.0.0.1', port: number = 3310) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.manager = new ClamAVManager();
|
||||
|
||||
// Listen to ClamAV logs
|
||||
this.manager.on('log', (event) => {
|
||||
if (event.type === 'scan') {
|
||||
console.log(`[ClamAV Scan] ${event.message}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async ensureContainerStarted(): Promise<void> {
|
||||
await this.manager.startContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans an in-memory Buffer using ClamAV daemon's INSTREAM command.
|
||||
*/
|
||||
public async scanBuffer(buffer: Buffer): Promise<{ isInfected: boolean; reason?: string }> {
|
||||
await this.ensureContainerStarted();
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = new net.Socket();
|
||||
|
||||
@ -85,6 +95,7 @@ export class ClamAvService {
|
||||
* Verifies the ClamAV daemon is reachable.
|
||||
*/
|
||||
public async verifyConnection(): Promise<boolean> {
|
||||
await this.ensureContainerStarted();
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = new net.Socket();
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
export * from './classes.smartantivirus.js';
|
||||
export * from './classes.smartantivirus.js';
|
||||
export * from './classes.clamav.manager.js';
|
@ -1,24 +1,39 @@
|
||||
// node native scope
|
||||
// Node.js built-in modules
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { exec, spawn } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { EventEmitter } from 'events';
|
||||
import net from 'net';
|
||||
|
||||
export {
|
||||
fs,
|
||||
path,
|
||||
}
|
||||
exec,
|
||||
spawn,
|
||||
promisify,
|
||||
EventEmitter,
|
||||
net
|
||||
};
|
||||
|
||||
// @push.rocks scope
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
|
||||
export {
|
||||
smartpath,
|
||||
smartfile,
|
||||
}
|
||||
expect,
|
||||
tap
|
||||
};
|
||||
|
||||
// third party scope
|
||||
// Third party scope
|
||||
import axios from 'axios';
|
||||
|
||||
export {
|
||||
axios,
|
||||
}
|
||||
axios
|
||||
};
|
||||
|
||||
// Common utilities
|
||||
export const execAsync = promisify(exec);
|
Reference in New Issue
Block a user