diff --git a/changelog.md b/changelog.md index 9781c09..ae9a33b 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # 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) Update readme with additional legal and trademark information diff --git a/package.json b/package.json index b688ee8..f1aba10 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "dependencies": { "@push.rocks/smartfile": "^11.1.5", "@push.rocks/smartpath": "^5.0.18", + "@push.rocks/smartstream": "^3.2.5", "axios": "^1.7.9", "tar": "^7.4.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5048cbd..d5b5569 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@push.rocks/smartpath': specifier: ^5.0.18 version: 5.0.18 + '@push.rocks/smartstream': + specifier: ^3.2.5 + version: 3.2.5 axios: specifier: ^1.7.9 version: 1.7.9 diff --git a/readme.md b/readme.md index aa997ac..c0a0f64 100644 --- a/readme.md +++ b/readme.md @@ -94,6 +94,35 @@ async function main() { 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:** 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`. diff --git a/test/test.ts b/test/test.ts index ec1b22a..41aef31 100644 --- a/test/test.ts +++ b/test/test.ts @@ -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 { setupClamAV, cleanupClamAV } from './helpers/clamav.helper.js'; diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 9d23c50..41a31dc 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { 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.' } diff --git a/ts/classes.smartantivirus.ts b/ts/classes.clamavservice.ts similarity index 56% rename from ts/classes.smartantivirus.ts rename to ts/classes.clamavservice.ts index 7253258..18217fc 100644 --- a/ts/classes.smartantivirus.ts +++ b/ts/classes.clamavservice.ts @@ -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); + } } \ No newline at end of file diff --git a/ts/index.ts b/ts/index.ts index a56edac..37c149f 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,2 +1,2 @@ -export * from './classes.smartantivirus.js'; +export * from './classes.clamavservice.js'; export * from './classes.clamav.manager.js'; \ No newline at end of file diff --git a/ts/plugins.ts b/ts/plugins.ts index c133838..2e3fb36 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -5,6 +5,8 @@ import { exec, spawn } from 'child_process'; import { promisify } from 'util'; import { EventEmitter } from 'events'; import net from 'net'; +import * as http from 'http'; +import * as https from 'https'; export { fs, @@ -13,19 +15,20 @@ export { spawn, promisify, EventEmitter, - net + net, + http, + https }; // @push.rocks scope import * as smartpath from '@push.rocks/smartpath'; import * as smartfile from '@push.rocks/smartfile'; -import { expect, tap } from '@push.rocks/tapbundle'; +import * as smartstream from '@push.rocks/smartstream'; export { smartpath, smartfile, - expect, - tap + smartstream, }; // Third party scope