feat(ClamAvService): Add stream scanning methods to ClamAvService

This commit is contained in:
Philipp Kunz 2025-02-05 10:49:46 +01:00
parent 8acfedd7f3
commit 6300843616
9 changed files with 123 additions and 7 deletions

View File

@ -1,5 +1,12 @@
# Changelog # Changelog
## 2025-02-05 - 1.2.0 - feat(ClamAvService)
Add stream scanning methods to ClamAvService
- Added scanStream method to support scanning NodeJS streams directly.
- Introduced scanWebStream method for scanning web resources as streams.
- Integrated stream scanning into existing ClamAvService class.
## 2025-02-03 - 1.1.2 - fix(documentation) ## 2025-02-03 - 1.1.2 - fix(documentation)
Update readme with additional legal and trademark information Update readme with additional legal and trademark information

View File

@ -25,6 +25,7 @@
"dependencies": { "dependencies": {
"@push.rocks/smartfile": "^11.1.5", "@push.rocks/smartfile": "^11.1.5",
"@push.rocks/smartpath": "^5.0.18", "@push.rocks/smartpath": "^5.0.18",
"@push.rocks/smartstream": "^3.2.5",
"axios": "^1.7.9", "axios": "^1.7.9",
"tar": "^7.4.3" "tar": "^7.4.3"
}, },

3
pnpm-lock.yaml generated
View File

@ -14,6 +14,9 @@ importers:
'@push.rocks/smartpath': '@push.rocks/smartpath':
specifier: ^5.0.18 specifier: ^5.0.18
version: 5.0.18 version: 5.0.18
'@push.rocks/smartstream':
specifier: ^3.2.5
version: 3.2.5
axios: axios:
specifier: ^1.7.9 specifier: ^1.7.9
version: 1.7.9 version: 1.7.9

View File

@ -94,6 +94,35 @@ async function main() {
main().catch(console.error); main().catch(console.error);
``` ```
### Streaming Scanning
The `ClamAvService` now also supports scanning streams directly using two new methods:
- `scanStream(stream: NodeJS.ReadableStream)`: Scans a NodeJS stream (local file streams, network streams, etc.)
- `scanWebStream(url: string)`: Fetches a web resource as a stream and scans it
#### Example Usage
```typescript
import { ClamAvService } from '@push.rocks/smartantivirus';
import { createReadStream } from 'fs';
async function main() {
const clamService = new ClamAvService('127.0.0.1', 3310);
// Example 1: Scanning a local file stream
const fileStream = createReadStream('path/to/local/file');
const streamResult = await clamService.scanStream(fileStream);
console.log('Stream Scan Result:', streamResult);
// Example 2: Scanning a web resource
const webResult = await clamService.scanWebStream('http://example.com/file');
console.log('Web Stream Scan Result:', webResult);
}
main().catch(console.error);
```
**Breaking Down the Example:** **Breaking Down the Example:**
1. **Initialization**: We start by creating an instance of the `ClamAvService` class. It takes two optional parameters: the host and port where your ClamAV daemon is running. By default, it assumes `127.0.0.1` and `3310`. 1. **Initialization**: We start by creating an instance of the `ClamAvService` class. It takes two optional parameters: the host and port where your ClamAV daemon is running. By default, it assumes `127.0.0.1` and `3310`.

View File

@ -1,4 +1,4 @@
import { expect, tap } from '../ts/plugins.js'; import { tap, expect } from '@push.rocks/tapbundle';
import * as smartantivirus from '../ts/index.js'; import * as smartantivirus from '../ts/index.js';
import { setupClamAV, cleanupClamAV } from './helpers/clamav.helper.js'; import { setupClamAV, cleanupClamAV } from './helpers/clamav.helper.js';

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartantivirus', name: '@push.rocks/smartantivirus',
version: '1.1.2', version: '1.2.0',
description: 'A Node.js package for integrating antivirus scanning capabilities using ClamAV, allowing in-memory file and data scanning.' description: 'A Node.js package for integrating antivirus scanning capabilities using ClamAV, allowing in-memory file and data scanning.'
} }

View File

@ -111,4 +111,77 @@ export class ClamAvService {
}); });
}); });
} }
/**
* Scans data from a NodeJS stream using ClamAV daemon's INSTREAM command.
*/
public async scanStream(stream: NodeJS.ReadableStream): Promise<{ isInfected: boolean; reason?: string }> {
await this.ensureContainerStarted();
return new Promise((resolve, reject) => {
const client = new net.Socket();
client.connect(this.port, this.host, () => {
console.log('Connected to ClamAV daemon for stream scanning');
client.write('zINSTREAM\0');
stream.on('data', (chunk: Buffer) => {
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
const sizeBuf = Buffer.alloc(4);
sizeBuf.writeUInt32BE(buf.length, 0);
client.write(sizeBuf);
client.write(buf);
});
stream.on('end', () => {
const endOfStream = Buffer.alloc(4);
endOfStream.writeUInt32BE(0, 0);
console.log('Stream ended, sending end-of-stream signal');
client.write(endOfStream);
});
stream.on('error', (err) => {
console.error('Error reading stream:', err);
reject(err);
});
});
client.on('data', (data) => {
const response = data.toString();
console.log('Raw Response from ClamAV (stream):', response);
const isInfected = response.includes('FOUND');
const reason = isInfected ? response.split('FOUND')[0].trim() : undefined;
resolve({ isInfected, reason });
client.end();
});
client.on('error', (err) => {
console.error('Error with ClamAV stream scanning:', err);
reject(err);
});
});
}
/**
* Scans a file from a web URL as a stream using ClamAV daemon's INSTREAM command.
*/
public async scanFileFromWebAsStream(url: string): Promise<{ isInfected: boolean; reason?: string }> {
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https') ? plugins.https : plugins.http;
protocol.get(url, (response) => {
this.scanStream(response).then(resolve).catch(reject);
}).on('error', (err) => {
console.error('Error fetching URL:', err);
reject(err);
});
});
}
/**
* Scans a web resource by URL using ClamAV daemon's INSTREAM command.
*/
public async scanWebStream(webstreamArg: ReadableStream): Promise<{ isInfected: boolean; reason?: string }> {
// Convert the web ReadableStream to a NodeJS ReadableStream
const nodeStream = plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webstreamArg);
return this.scanStream(nodeStream);
}
} }

View File

@ -1,2 +1,2 @@
export * from './classes.smartantivirus.js'; export * from './classes.clamavservice.js';
export * from './classes.clamav.manager.js'; export * from './classes.clamav.manager.js';

View File

@ -5,6 +5,8 @@ import { exec, spawn } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import net from 'net'; import net from 'net';
import * as http from 'http';
import * as https from 'https';
export { export {
fs, fs,
@ -13,19 +15,20 @@ export {
spawn, spawn,
promisify, promisify,
EventEmitter, EventEmitter,
net net,
http,
https
}; };
// @push.rocks scope // @push.rocks scope
import * as smartpath from '@push.rocks/smartpath'; import * as smartpath from '@push.rocks/smartpath';
import * as smartfile from '@push.rocks/smartfile'; import * as smartfile from '@push.rocks/smartfile';
import { expect, tap } from '@push.rocks/tapbundle'; import * as smartstream from '@push.rocks/smartstream';
export { export {
smartpath, smartpath,
smartfile, smartfile,
expect, smartstream,
tap
}; };
// Third party scope // Third party scope