22
33var _ = require ( 'lodash' ) ;
44var mongoose = require ( 'mongoose' ) ;
5- var clamav = require ( 'clamav.js' ) ;
6- var _serviceHost = process . env . CLAMAV_SERVICE_HOST || '127.0.0.1' ;
7- var _servicePort = process . env . CLAMAV_SERVICE_PORT || '3310' ;
5+ var NodeClam = require ( 'clamscan' ) ;
86var MAX_LIMIT = 1000 ;
97const defaultLog = require ( 'winston' ) . loggers . get ( 'default' ) ;
108var DEFAULT_PAGESIZE = 100 ;
119
10+ // ClamAV scanner instance (initialized on first use)
11+ let clamScanner = null ;
12+
13+ /**
14+ * Get or initialize the ClamAV scanner instance.
15+ * Uses TCP connection to remote ClamAV daemon.
16+ */
17+ async function getClamScanner ( ) {
18+ if ( clamScanner ) {
19+ return clamScanner ;
20+ }
21+
22+ const serviceHost = process . env . CLAMAV_SERVICE_HOST || '127.0.0.1' ;
23+ const servicePort = parseInt ( process . env . CLAMAV_SERVICE_PORT , 10 ) || 3310 ;
24+
25+ try {
26+ clamScanner = await new NodeClam ( ) . init ( {
27+ debugMode : false ,
28+ clamdscan : {
29+ host : serviceHost ,
30+ port : servicePort ,
31+ timeout : 60000 ,
32+ localFallback : false ,
33+ active : true ,
34+ } ,
35+ clamscan : {
36+ active : false , // Don't use local binary
37+ } ,
38+ preference : 'clamdscan' ,
39+ } ) ;
40+ defaultLog . info ( `ClamAV scanner initialized: ${ serviceHost } :${ servicePort } ` ) ;
41+ return clamScanner ;
42+ } catch ( err ) {
43+ defaultLog . error ( `Failed to initialize ClamAV scanner: ${ err . message } ` ) ;
44+ throw err ;
45+ }
46+ }
47+
1248exports . buildQuery = function ( property , values , query ) {
1349 var oids = [ ] ;
1450 if ( _ . isArray ( values ) ) {
@@ -32,34 +68,38 @@ exports.getBasePath = function (protocol, host) {
3268 return protocol + '://' + host ;
3369} ;
3470
35- // MBL: TODO Make this event driven instead of synchronous?
36- exports . avScan = function ( buffer ) {
37- return new Promise ( function ( resolve ) {
38- var stream = require ( 'stream' ) ;
39- // Initiate the source
40- var bufferStream = new stream . PassThrough ( ) ;
41- // Write your buffer
71+ /**
72+ * Scan a buffer for viruses using ClamAV.
73+ * @param {Buffer } buffer - The file buffer to scan
74+ * @returns {Promise<boolean> } - true if file is clean, false if infected or error
75+ */
76+ exports . avScan = async function ( buffer ) {
77+ const stream = require ( 'stream' ) ;
78+ const serviceHost = process . env . CLAMAV_SERVICE_HOST || '127.0.0.1' ;
79+ const servicePort = process . env . CLAMAV_SERVICE_PORT || '3310' ;
80+
81+ try {
82+ const clam = await getClamScanner ( ) ;
83+
84+ // Create a readable stream from the buffer
85+ const bufferStream = new stream . PassThrough ( ) ;
4286 bufferStream . end ( buffer ) ;
4387
44- clamav . ping ( _servicePort , _serviceHost , 1000 , function ( err ) {
45- if ( err ) {
46- defaultLog . warn ( 'ClamAV service: ' + _serviceHost + ':' + _servicePort + ' is not available[' + err + ']' ) ;
47- resolve ( false ) ;
48- } else {
49- clamav . createScanner ( _servicePort , _serviceHost )
50- . scan ( bufferStream , function ( err , object , malicious ) {
51- if ( err ) {
52- defaultLog . info ( 'Error:' , err ) ;
53- resolve ( false ) ;
54- } else if ( malicious ) {
55- resolve ( false ) ;
56- } else {
57- resolve ( true ) ;
58- }
59- } ) ;
60- }
61- } ) ;
62- } ) ;
88+ const { isInfected, viruses } = await clam . scanStream ( bufferStream ) ;
89+
90+ if ( isInfected ) {
91+ defaultLog . warn ( `File is infected with: ${ viruses . join ( ', ' ) } ` ) ;
92+ return false ;
93+ }
94+
95+ defaultLog . info ( 'File passed virus scan' ) ;
96+ return true ;
97+ } catch ( err ) {
98+ defaultLog . warn ( `ClamAV service: ${ serviceHost } :${ servicePort } scan failed: ${ err . message } ` ) ;
99+ // Reset scanner instance on error so it can be re-initialized
100+ clamScanner = null ;
101+ return false ;
102+ }
63103} ;
64104
65105exports . getSkipLimitParameters = function ( pageSize , pageNum ) {
0 commit comments