Gets a listing of all files (no directories) within a given path.
31 | By default, it will retrieve files recursively.
32 |
33 |
34 |
35 |
36 |
37 | ## NodeClam
38 | NodeClam class definition.
39 |
40 | **Kind**: global class
41 | **Access**: public
42 |
43 | * [NodeClam](#NodeClam)
44 | * [new NodeClam()](#new_NodeClam_new)
45 | * [.init([options], [cb])](#NodeClam+init) ⇒ Promise.<object>
46 | * [.reset([options], [cb])](#NodeClam+reset) ⇒ Promise.<object>
47 | * [.getVersion([cb])](#NodeClam+getVersion) ⇒ Promise.<string>
48 | * [.isInfected(file, [cb])](#NodeClam+isInfected) ⇒ Promise.<object>
49 | * [.passthrough()](#NodeClam+passthrough) ⇒ Transform
50 | * [.scanFile(file, [cb])](#NodeClam+scanFile) ⇒ Promise.<object>
51 | * [.scanFiles(files, [endCb], [fileCb])](#NodeClam+scanFiles) ⇒ Promise.<object>
52 | * [.scanDir(path, [endCb], [fileCb])](#NodeClam+scanDir) ⇒ Promise.<object>
53 | * [.scanStream(stream, [cb])](#NodeClam+scanStream) ⇒ Promise.<object>
54 |
55 |
56 |
57 | ### new NodeClam()
58 | This sets up all the defaults of the instance but does not
59 | necessarily return an initialized instance. Use `.init` for that.
60 |
61 |
62 |
63 | ### nodeClam.init([options], [cb]) ⇒ Promise.<object>
64 | Initialization method.
65 |
66 | **Kind**: instance method of [NodeClam](#NodeClam)
67 | **Returns**: Promise.<object> - An initated instance of NodeClam
68 | **Access**: public
69 |
70 | | Param | Type | Default | Description |
71 | | --- | --- | --- | --- |
72 | | [options] | object | | User options for the Clamscan module |
73 | | [options.removeInfected] | boolean | false | If true, removes infected files when found |
74 | | [options.quarantineInfected] | boolean \| string | false | If not false, should be a string to a path to quarantine infected files |
75 | | [options.scanLog] | string | null | Path to a writeable log file to write scan results into |
76 | | [options.debugMode] | boolean | false | If true, *a lot* of info will be spewed to the logs |
77 | | [options.fileList] | string | null | Path to file containing list of files to scan (for `scanFiles` method) |
78 | | [options.scanRecursively] | boolean | true | If true, deep scan folders recursively (for `scanDir` method) |
79 | | [options.clamscan] | object | | Options specific to the clamscan binary |
80 | | [options.clamscan.path] | string | "'/usr/bin/clamscan'" | Path to clamscan binary on your server |
81 | | [options.clamscan.db] | string | null | Path to a custom virus definition database |
82 | | [options.clamscan.scanArchives] | boolean | true | If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...) |
83 | | [options.clamscan.active] | boolean | true | If true, this module will consider using the clamscan binary |
84 | | [options.clamdscan] | object | | Options specific to the clamdscan binary |
85 | | [options.clamdscan.socket] | string | false | Path to socket file for connecting via TCP |
86 | | [options.clamdscan.host] | string | false | IP of host to connec to TCP interface |
87 | | [options.clamdscan.port] | string | false | Port of host to use when connecting via TCP interface |
88 | | [options.clamdscan.timeout] | number | 60000 | Timeout for scanning files |
89 | | [options.clamdscan.localFallback] | boolean | false | If false, do not fallback to a local binary-method of scanning |
90 | | [options.clamdscan.path] | string | "'/usr/bin/clamdscan'" | Path to the `clamdscan` binary on your server |
91 | | [options.clamdscan.configFile] | string | null | Specify config file if it's in an usual place |
92 | | [options.clamdscan.multiscan] | boolean | true | If true, scan using all available cores |
93 | | [options.clamdscan.reloadDb] | boolean | false | If true, will re-load the DB on ever call (slow) |
94 | | [options.clamdscan.active] | boolean | true | If true, this module will consider using the `clamdscan` binary |
95 | | [options.clamdscan.bypassTest] | boolean | false | If true, check to see if socket is avaliable |
96 | | [options.clamdscan.tls] | boolean | false | If true, connect to a TLS-Termination proxy in front of ClamAV |
97 | | [options.preference] | object | 'clamdscan' | If preferred binary is found and active, it will be used by default |
98 | | [cb] | function | | Callback method. Prototype: `(err, )` |
99 |
100 | **Example**
101 | ```js
102 | const NodeClam = require('clamscan');
103 | const ClamScan = new NodeClam().init({
104 | removeInfected: false,
105 | quarantineInfected: false,
106 | scanLog: null,
107 | debugMode: false,
108 | fileList: null,
109 | scanRecursively: true,
110 | clamscan: {
111 | path: '/usr/bin/clamscan',
112 | db: null,
113 | scanArchives: true,
114 | active: true
115 | },
116 | clamdscan: {
117 | socket: false,
118 | host: false,
119 | port: false,
120 | timeout: 60000,
121 | localFallback: false,
122 | path: '/usr/bin/clamdscan',
123 | configFile: null,
124 | multiscan: true,
125 | reloadDb: false,
126 | active: true,
127 | bypassTest: false,
128 | },
129 | preference: 'clamdscan'
130 | });
131 | ```
132 |
133 |
134 | ### nodeClam.reset([options], [cb]) ⇒ Promise.<object>
135 | Allows one to create a new instances of clamscan with new options.
136 |
137 | **Kind**: instance method of [NodeClam](#NodeClam)
138 | **Returns**: Promise.<object> - A reset instance of NodeClam
139 | **Access**: public
140 |
141 | | Param | Type | Default | Description |
142 | | --- | --- | --- | --- |
143 | | [options] | object | {} | Same options as the `init` method |
144 | | [cb] | function | | What to do after reset (repsponds with reset instance of NodeClam) |
145 |
146 |
147 |
148 | ### nodeClam.getVersion([cb]) ⇒ Promise.<string>
149 | Establish the clamav version of a local or remote clamav daemon.
150 |
151 | **Kind**: instance method of [NodeClam](#NodeClam)
152 | **Returns**: Promise.<string> - - The version of ClamAV that is being interfaced with
153 | **Access**: public
154 |
155 | | Param | Type | Description |
156 | | --- | --- | --- |
157 | | [cb] | function | What to do when version is established |
158 |
159 | **Example**
160 | ```js
161 | // Callback example
162 | clamscan.getVersion((err, version) => {
163 | if (err) return console.error(err);
164 | console.log(`ClamAV Version: ${version}`);
165 | });
166 |
167 | // Promise example
168 | const clamscan = new NodeClam().init();
169 | const version = await clamscan.getVersion();
170 | console.log(`ClamAV Version: ${version}`);
171 | ```
172 |
173 |
174 | ### nodeClam.isInfected(file, [cb]) ⇒ Promise.<object>
175 | This method allows you to scan a single file. It supports a callback and Promise API.
176 | If no callback is supplied, a Promise will be returned. This method will likely
177 | be the most common use-case for this module.
178 |
179 | **Kind**: instance method of [NodeClam](#NodeClam)
180 | **Returns**: Promise.<object> - Object like: `{ file: String, isInfected: Boolean, viruses: Array }`
181 | **Access**: public
182 |
183 | | Param | Type | Default | Description |
184 | | --- | --- | --- | --- |
185 | | file | string | | Path to the file to check |
186 | | [cb] | function | | What to do after the scan |
187 |
188 | **Example**
189 | ```js
190 | // Callback Example
191 | clamscan.isInfected('/a/picture/for_example.jpg', (err, file, isInfected, viruses) => {
192 | if (err) return console.error(err);
193 |
194 | if (isInfected) {
195 | console.log(`${file} is infected with ${viruses.join(', ')}.`);
196 | }
197 | });
198 |
199 | // Promise Example
200 | clamscan.isInfected('/a/picture/for_example.jpg').then(result => {
201 | const {file, isInfected, viruses} = result;
202 | if (isInfected) console.log(`${file} is infected with ${viruses.join(', ')}.`);
203 | }).then(err => {
204 | console.error(err);
205 | });
206 |
207 | // Async/Await Example
208 | const {file, isInfected, viruses} = await clamscan.isInfected('/a/picture/for_example.jpg');
209 | ```
210 |
211 |
212 | ### nodeClam.passthrough() ⇒ Transform
213 | Returns a PassthroughStream object which allows you to
214 | pipe a ReadbleStream through it and on to another output. In the case of this
215 | implementation, it's actually forking the data to also
216 | go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to
217 | the output if that chunk was successfully sent to and received by ClamAV.
218 | The PassthroughStream object returned from this method has a special event
219 | that is emitted when ClamAV finishes scanning the streamed data (`scan-complete`)
220 | so that you can decide if there's anything you need to do with the final output
221 | destination (ex. delete a file or S3 object).
222 |
223 | **Kind**: instance method of [NodeClam](#NodeClam)
224 | **Returns**: Transform - A Transform stream for piping a Readable stream into
225 | **Access**: public
226 | **Example**
227 | ```js
228 | const NodeClam = require('clamscan');
229 |
230 | // You'll need to specify your socket or TCP connection info
231 | const clamscan = new NodeClam().init({
232 | clamdscan: {
233 | socket: '/var/run/clamd.scan/clamd.sock',
234 | host: '127.0.0.1',
235 | port: 3310,
236 | }
237 | });
238 |
239 | // For example's sake, we're using the Axios module
240 | const axios = require('axios');
241 |
242 | // Get a readable stream for a URL request
243 | const input = axios.get(someUrl);
244 |
245 | // Create a writable stream to a local file
246 | const output = fs.createWriteStream(someLocalFile);
247 |
248 | // Get instance of this module's PassthroughStream object
249 | const av = clamscan.passthrough();
250 |
251 | // Send output of Axios stream to ClamAV.
252 | // Send output of Axios to `someLocalFile` if ClamAV receives data successfully
253 | input.pipe(av).pipe(output);
254 |
255 | // What happens when scan is completed
256 | av.on('scan-complete', result => {
257 | const {isInfected, viruses} = result;
258 | // Do stuff if you want
259 | });
260 |
261 | // What happens when data has been fully written to `output`
262 | output.on('finish', () => {
263 | // Do stuff if you want
264 | });
265 |
266 | // NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications
267 | ```
268 |
269 |
270 | ### nodeClam.scanFile(file, [cb]) ⇒ Promise.<object>
271 | Just an alias to `isInfected`. See docs for that for usage examples.
272 |
273 | **Kind**: instance method of [NodeClam](#NodeClam)
274 | **Returns**: Promise.<object> - Object like: `{ file: String, isInfected: Boolean, viruses: Array }`
275 | **Access**: public
276 |
277 | | Param | Type | Description |
278 | | --- | --- | --- |
279 | | file | string | Path to the file to check |
280 | | [cb] | function | What to do after the scan |
281 |
282 |
283 |
284 | ### nodeClam.scanFiles(files, [endCb], [fileCb]) ⇒ Promise.<object>
285 | Scans an array of files or paths. You must provide the full paths of the
286 | files and/or paths. Also enables the ability to scan a file list.
287 |
288 | This is essentially a wrapper for isInfected that simplifies the process
289 | of scanning many files or directories.
290 |
291 | **NOTE:** The only way to get per-file notifications is through the callback API.
292 |
293 | **Kind**: instance method of [NodeClam](#NodeClam)
294 | **Returns**: Promise.<object> - Object like: `{ goodFiles: Array, badFiles: Array, errors: Object, viruses: Array }`
295 | **Access**: public
296 |
297 | | Param | Type | Default | Description |
298 | | --- | --- | --- | --- |
299 | | files | Array | | A list of files or paths (full paths) to be scanned |
300 | | [endCb] | function | | What to do after the scan completes |
301 | | [fileCb] | function | | What to do after each file has been scanned |
302 |
303 | **Example**
304 | ```js
305 | // Callback Example
306 | const scanStatus = {
307 | good: 0,
308 | bad: 0
309 | };
310 | const files = [
311 | '/path/to/file/1.jpg',
312 | '/path/to/file/2.mov',
313 | '/path/to/file/3.rb'
314 | ];
315 | clamscan.scanFiles(files, (err, goodFiles, badFiles, viruses) => {
316 | if (err) return console.error(err);
317 | if (badFiles.length > 0) {
318 | console.log({
319 | msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
320 | badFiles,
321 | goodFiles,
322 | viruses,
323 | });
324 | } else {
325 | res.send({msg: "Everything looks good! No problems here!."});
326 | }
327 | }, (err, file, isInfected, viruses) => {
328 | ;(isInfected ? scanStatus.bad++ : scanStatus.good++);
329 | console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);
330 | console.log('Scan Status: ', `${(scanStatus.bad + scanStatus.good)}/${files.length}`);
331 | });
332 |
333 | // Async/Await method
334 | const {goodFiles, badFiles, errors, viruses} = await clamscan.scanFiles(files);
335 | ```
336 |
337 |
338 | ### nodeClam.scanDir(path, [endCb], [fileCb]) ⇒ Promise.<object>
339 | Scans an entire directory. Provides 3 params to end callback: Error, path
340 | scanned, and whether its infected or not. To scan multiple directories, pass
341 | them as an array to the `scanFiles` method.
342 |
343 | This obeys your recursive option even for `clamdscan` which does not have a native
344 | way to turn this feature off. If you have multiple paths, send them in an array
345 | to `scanFiles`.
346 |
347 | NOTE: While possible, it is NOT advisable to use the `fileCb` parameter when
348 | using the `clamscan` binary. Doing so with `clamdscan` is okay, however. This
349 | method also allows for non-recursive scanning with the clamdscan binary.
350 |
351 | **Kind**: instance method of [NodeClam](#NodeClam)
352 | **Returns**: Promise.<object> - Object like: `{ path: String, isInfected: Boolean, goodFiles: Array, badFiles: Array, viruses: Array }`
353 | **Access**: public
354 |
355 | | Param | Type | Default | Description |
356 | | --- | --- | --- | --- |
357 | | path | string | | The directory to scan files of |
358 | | [endCb] | function | | What to do when all files have been scanned |
359 | | [fileCb] | function | | What to do after each file has been scanned |
360 |
361 | **Example**
362 | ```js
363 | // Callback Method
364 | clamscan.scanDir('/some/path/to/scan', (err, goodFiles, badFiles, viruses, numGoodFiles) {
365 | if (err) return console.error(err);
366 |
367 | if (badFiles.length > 0) {
368 | console.log(`${path} was infected. The offending files (${badFiles.map(v => `${v.file} (${v.virus})`).join (', ')}) have been quarantined.`);
369 | console.log(`Viruses Found: ${viruses.join(', ')}`);
370 | } else {
371 | console.log('Everything looks good! No problems here!.');
372 | }
373 | });
374 |
375 | // Async/Await Method
376 | const {path, isInfected, goodFiles, badFiles, viruses} = await clamscan.scanDir('/some/path/to/scan');
377 | ```
378 |
379 |
380 | ### nodeClam.scanStream(stream, [cb]) ⇒ Promise.<object>
381 | Allows you to scan a binary stream.
382 |
383 | **NOTE:** This method will only work if you've configured the module to allow the
384 | use of a TCP or UNIX Domain socket. In other words, this will not work if you only
385 | have access to a local ClamAV binary.
386 |
387 | **Kind**: instance method of [NodeClam](#NodeClam)
388 | **Returns**: Promise.<object> - Object like: `{ file: String, isInfected: Boolean, viruses: Array } `
389 | **Access**: public
390 |
391 | | Param | Type | Description |
392 | | --- | --- | --- |
393 | | stream | Readable | A readable stream to scan |
394 | | [cb] | function | What to do when the socket response with results |
395 |
396 | **Example**
397 | ```js
398 | const NodeClam = require('clamscan');
399 |
400 | // You'll need to specify your socket or TCP connection info
401 | const clamscan = new NodeClam().init({
402 | clamdscan: {
403 | socket: '/var/run/clamd.scan/clamd.sock',
404 | host: '127.0.0.1',
405 | port: 3310,
406 | }
407 | });
408 | const Readable = require('stream').Readable;
409 | const rs = Readable();
410 |
411 | rs.push('foooooo');
412 | rs.push('barrrrr');
413 | rs.push(null);
414 |
415 | // Callback Example
416 | clamscan.scanStream(stream, (err, { isInfected, viruses }) => {
417 | if (err) return console.error(err);
418 | if (isInfected) return console.log('Stream is infected! Booo!', viruses);
419 | console.log('Stream is not infected! Yay!');
420 | });
421 |
422 | // Async/Await Example
423 | const { isInfected, viruses } = await clamscan.scanStream(stream);
424 | ```
425 |
426 |
427 | ## NodeClamError
428 | Clamscan-specific extension of the Javascript Error object
429 |
430 | **NOTE**: If string is passed to first param, it will be `msg` and data will be `{}`
431 |
432 | **Kind**: global class
433 |
434 |
435 | ### new NodeClamError(data, ...params)
436 | Creates a new instance of a NodeClamError.
437 |
438 |
439 | | Param | Type | Description |
440 | | --- | --- | --- |
441 | | data | object | Additional data we might want to have access to on error |
442 | | ...params | any | The usual params you'd pass to create an Error object |
443 |
444 |
445 |
446 | ## NodeClamTransform
447 | A NodeClam - specific Transform extension that coddles
448 | chunks into the correct format for a ClamAV socket.
449 |
450 | **Kind**: global class
451 |
452 | * [NodeClamTransform](#NodeClamTransform)
453 | * [new NodeClamTransform(options, debugMode)](#new_NodeClamTransform_new)
454 | * [._transform(chunk, encoding, cb)](#NodeClamTransform+_transform)
455 | * [._flush(cb)](#NodeClamTransform+_flush)
456 |
457 |
458 |
459 | ### new NodeClamTransform(options, debugMode)
460 | Creates a new instance of NodeClamTransorm.
461 |
462 |
463 | | Param | Type | Default | Description |
464 | | --- | --- | --- | --- |
465 | | options | object | | Optional overrides to defaults (same as Node.js Transform) |
466 | | debugMode | boolean | false | If true, do special debug logging |
467 |
468 |
469 |
470 | ### nodeClamTransform.\_transform(chunk, encoding, cb)
471 | Actually does the transorming of the data for ClamAV.
472 |
473 | **Kind**: instance method of [NodeClamTransform](#NodeClamTransform)
474 |
475 | | Param | Type | Description |
476 | | --- | --- | --- |
477 | | chunk | Buffer | The piece of data to push onto the stream |
478 | | encoding | string | The encoding of the chunk |
479 | | cb | function | What to do when done pushing chunk |
480 |
481 |
482 |
483 | ### nodeClamTransform.\_flush(cb)
484 | This will flush out the stream when all data has been received.
485 |
486 | **Kind**: instance method of [NodeClamTransform](#NodeClamTransform)
487 |
488 | | Param | Type | Description |
489 | | --- | --- | --- |
490 | | cb | function | What to do when done |
491 |
492 |
493 |
494 | ## ping ⇒ Promise.<object>
495 | Quick check to see if the remote/local socket is working. Callback/Resolve
496 | response is an instance to a ClamAV socket client.
497 |
498 | **Kind**: global variable
499 | **Returns**: Promise.<object> - A copy of the Socket/TCP client
500 | **Access**: public
501 |
502 | | Param | Type | Description |
503 | | --- | --- | --- |
504 | | [cb] | function | What to do after the ping |
505 |
506 |
507 |
508 | ## getFiles(dir, [recursive]) ⇒ Array
509 | Gets a listing of all files (no directories) within a given path.
510 | By default, it will retrieve files recursively.
511 |
512 | **Kind**: global function
513 | **Returns**: Array - - List of all requested path files
514 |
515 | | Param | Type | Default | Description |
516 | | --- | --- | --- | --- |
517 | | dir | string | | The directory to get all files of |
518 | | [recursive] | boolean | true | If true (default), get all files recursively; False: only get files directly in path |
519 |
520 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # Changes
2 |
3 | This file is a manually maintained list of changes for each release. Feel free to add your changes here when sending pull requests. Also send corrections if you spot any mistakes.
4 |
5 | ## 0.2.1
6 |
7 | - ClamAV returns an exit code 1 when it detects a virus but `exec` was interpreting that response as an error. Checking the response with type-sensitive equivalence resolves this bug.
8 |
9 | ## 0.2.2
10 |
11 | - Fixed documentation
12 |
13 | ## 0.4.0 (2014-11-19)
14 |
15 | - Corrected the installation instructions for `clamav`. Thank you @jshamley!
16 | - Fixed major bug preventing the `scan_dir` method from working properly
17 | - Corrected documentation describing how to instantiate this module.
18 |
19 | ## 0.5.0 (2014-12-19)
20 |
21 | - Deprecated the `quarantine_path` option. Please only use `quarantine_infected` for now on.
22 | - Updated documentation to reflect above change.
23 |
24 | ## 0.6.0 (2015-01-02)
25 |
26 | **NOTE:** There are some breaking changes on this release. Since this is still a pre-version 1 release, I decided to only do a minor bump to 0.4.0
27 |
28 | - The ability to run "forked" instances of `clamscan` has been removed because of irregularities with different systems--namely if you had `max_forks` set to 3, it would sometimes only scan the first or last file in the group... not good.
29 | - Added the ability to use `clamdscan`. This ultimately negates the downside of removing the forking capability mentioned in item one. This is a really big improvement (many orders of magnitude) if your system has access to the `clamdscan` daemon.
30 | - Added a `file_list` option allowing one to specify a text file that lists (one per line) paths to files to be scanned. This is great if you need to scan hundreds or thousands of random files.
31 | - `clam_path` option has been moved to `clam.path`
32 | - `db` option has been moved to `clam.db`
33 | - `scan_archives` option has been moved to `clam.scan_archives`
34 | - `scan_files` now supports directories as well and will obey your `scan_recursively` option.
35 |
36 | ## 0.6.1 (2015-01-05)
37 |
38 | - Updated description in package.json file.
39 |
40 | ## 0.6.2 (2015-01-05)
41 |
42 | - Fixed major bug in the scan_files method that was causing it to only scan half the files passed to it.
43 |
44 | ## 0.6.3 (2015-01-05)
45 |
46 | - Removed the unnecessary "index_old.js" file put there for reference during the 0.5.0 -> 0.6.0 semi-rewrite.
47 |
48 | ## 0.6.4 (2015-01-26)
49 |
50 | - Fixed error messages
51 |
52 | ## 0.7.0 (2015-06-01)
53 |
54 | - Fixed a bug caused by not passing a `file_cb` paramter to the `scan_file` method. Thanks nicolaspeixoto!
55 | - Added tests
56 | - Fixed poor validation of method parameters
57 | - Changed API of `scan_dir` such that the paramaters passed to the `end_cb` are different in certain defined situations. See the "NOTE" section of the `scan_dir` documentation for details.
58 | - Changed `err` paramter in all callbacks from a simple string to a proper javascript `Error` object.
59 | - Added documentation for how to use a file_list file for scanning.
60 |
61 | ## 0.7.1 (2015-06-05)
62 |
63 | - Added node dependency of > 0.12 to `package.json` file
64 |
65 | ## 0.8.0 (2015-06-05)
66 |
67 | - Removed item causing node > 0.12 dependency.
68 | - Removed dependency of node > 0.12 in `package.json` file.
69 |
70 | ## 0.8.1 (2015-06-09)
71 |
72 | - Fixed check for database file. Issue #6
73 |
74 | ## 0.8.2 (2015-08-14)
75 |
76 | - Updated to `execFile` instead of `exec`
77 | - Improved test suite
78 |
79 | ## 0.9.0-beta (2015-07-01) - Never Released
80 |
81 | - Added support for TCP/UNIX Domain socket communication to local or remote clamav services.
82 | - Added a `get_version` method.
83 | - NULL is now returned to the third parameter of the `is_infected` when file is neither infected or clean (i.e. on unexpected response)
84 | - Created alias: `scan_file` for `is_infected`.
85 | - Created a `scan_stream` method.
86 | - Minor code clean-up
87 |
88 | ## 1.0.0 (2019-05-02)
89 |
90 | This is a huge major release in which this module was essentially completely re-written. This version introduces some breaking changes and major new features. Please read the release notes below carefully.
91 |
92 | - Now requires at least Node v10.0.0
93 | - Code re-written in ES2018 code
94 | - Now supports a hybrid Promise/Callback API (supports async/await)
95 | - Now properly supports TCP/UNIX Domain socket communication to local or remote clamav services (with optional fallback to local binary via child process).
96 | - Added new `scan_stream` method which allows you to pass an input stream.
97 | - Added new `get_version` method which allows you to check the version of ClamAV that you'll be communicating with.
98 | - Added new `passthrough` method which allows you to pipe a stream "through" the clamscan module and on to another destination (ex. S3).
99 | - Added new alias `scan_file` that points to `is_infected`.
100 | - In order to provide the name of any viruses found, a new standard `viruses` array is now be provided to the callback for:
101 |
102 | - `is_infected` & `scan_file` methods (callback format: `(err, file, is_infected, viruses) => { ... }`).
103 | - `scan_files` method (callback format: `(err, good_files, bad_files, error_files, viruses) => { ... }`).
104 | - `scan_dir` method (callback format: `(err, good_files, bad_files, viruses) => { ... }`).
105 |
106 | - In all cases, the `viruses` parameter will be an empty array on error or when no viruses are found.
107 |
108 | - `scan_files` now has another additional parameter in its callback:
109 |
110 | - `error_files`: An object keyed by the filenames that presented errors while scanning. The value of those keys will be the error message for that file.
111 |
112 | - Introduces new API to instantiate the module (NOTE: The old way will no longer work! See below for more info).
113 |
114 | ### API Changes with 1.0.0:
115 |
116 | For some full-fledged examples of how the new API works, checkout the `/examples` directory in the module root directory.
117 |
118 | #### Module Initialization
119 |
120 | ##### Pre-1.0.0
121 |
122 | ```javascript
123 | const clamscan = require('clamscan')(options);
124 | ```
125 |
126 | ##### 1.0.0
127 |
128 | **NOTE:** Due to the new asynchronous nature of the checks that are performed upon initialization of the module, the initialization method now returns a Promise instead of the actual instantiated object. Resolving the Promise with `then` will return the object like before.
129 |
130 | ```javascript
131 | const NodeClam = require('clamscan');
132 | const ClamScan = new NodeClam().init(options);
133 | ```
134 |
135 | #### Making Method Calls
136 |
137 | ##### Pre-1.0.0
138 |
139 | ```javascript
140 | clamscan.is_infected('/path/to/file.txt', (err, file, is_infected) => {
141 | // Do stuff
142 | });
143 | ```
144 |
145 | ##### 1.0.0
146 |
147 | ```javascript
148 | ClamScan.then(clamscan => {
149 | clamscan.is_infected('/path/to/file.txt', (err, file, is_infected, viruses) => {
150 | // Do stuff
151 | });
152 | });
153 | ```
154 |
155 | If you prefer the async/await style of coding:
156 |
157 | ```javascript
158 | ;(async () => {
159 | const clamscan = await new NodeClam().init(options);
160 | clamscan.is_infected('/path/to/file.txt', (err, file, is_infected, viruses) => {
161 | // Do stuff
162 | });
163 | })();
164 | ```
165 |
166 | #### New Way to Get Results
167 |
168 | ##### Pre-1.0.0
169 |
170 | The only way to get results/errors in pre-1.0.0 was through callbacks.
171 |
172 | ```javascript
173 | const clamscan = require('clamscan')(options);
174 | clamscan.scan_dir('/path/to/directory', (err, good_files, bad_files) => {
175 | // Do stuff inside callback
176 | });
177 | ```
178 |
179 | ##### 1.0.0
180 |
181 | In version 1.0.0 and beyond, you will now be able to use Promises as well (and, of course, async/await).
182 |
183 | ###### Promises
184 |
185 | ```javascript
186 | const ClamScan = new NodeClam().init(options);
187 | ClamScan.then(clamscan =>
188 | clamscan.scan_dir('/path/to/directory').then(result => {
189 | const {good_files, bad_files} = result;
190 | // Do stuff
191 | }).catch(err => {
192 | // Handle scan error
193 | });
194 | }).catch(err => {
195 | // Handle initialization error
196 | });
197 | ```
198 |
199 | ###### Async/Await
200 |
201 | ```javascript
202 | ;(async () => {
203 | try {
204 | const clamscan = await new NodeClam().init(options);
205 | const {good_files, bad_files} = await clamscan.scan_dir('/path/to/directory');
206 | // Do stuff
207 | } catch (err) {
208 | // Handle any error
209 | }
210 | })();
211 | ```
212 |
213 | #### New Methods
214 |
215 | ##### scan_stream
216 |
217 | The `scan_stream` method allows you supply a readable stream to have it scanned. Theoretically any stream can be scanned this way. Like all methods, it supports callback and Promise response styles (full documentation is in README).
218 |
219 | ###### Basic Promise (async/await) Example:
220 |
221 | ```javascript
222 | ;(async () => {
223 | try {
224 | const clamscan = await new NodeClam().init(options);
225 | const stream = new Readable();
226 | rs.push('foooooo');
227 | rs.push('barrrrr');
228 | rs.push(null);
229 |
230 | const {is_infected, viruses} = await clamscan.scan_stream(stream);
231 |
232 | // Do stuff
233 | } catch (err) {
234 | // Handle any error
235 | }
236 | })();
237 | ```
238 |
239 | ###### Basic Callback Example:
240 |
241 | ```javascript
242 | ;(async () => {
243 | try {
244 | const clamscan = await new NodeClam().init(options);
245 | const stream = new Readable();
246 | rs.push('foooooo');
247 | rs.push('barrrrr');
248 | rs.push(null);
249 |
250 | clamscan.scan_stream(stream, (err, results) => {
251 | if (err) {
252 | // Handle error
253 | } else {
254 | const {is_infected, viruses} = results;
255 | // Do stuff
256 | }
257 | });
258 |
259 | // Do stuff
260 | } catch (err) {
261 | // Handle any error
262 | }
263 | })();
264 | ```
265 |
266 | ##### passthrough
267 |
268 | The `passthrough` method allows you supply a readable stream that will be "passed-through" the clamscan module and onto another destination. In reality, the passthrough method works more like a fork stream whereby the input stream is simultaneously streamed to ClamAV and whatever is the next destination. Events are created when ClamAV is done and/or when viruses are detected so that you can decide what to do with the data on the next destination (delete if virus detected, for instance). Data is only passed through to the next generation if the data has been successfully received by ClamAV. If anything halts the data going to ClamAV (including issues caused by ClamAV), the entire pipeline is halted and events are fired.
269 |
270 | Normally, a file is uploaded and then scanned. This method should theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to disk. Your mileage my vary.
271 |
272 | This method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (full documentation is in README).
273 |
274 | ###### Basic Example:
275 |
276 | ```javascript
277 | ;(async () => {
278 | try {
279 | const clamscan = await new NodeClam().init(options);
280 | const request = require('request');
281 | const input = request.get(some_url);
282 | const output = fs.createWriteStream(some_local_file);
283 | const av = clamscan.passthrough();
284 |
285 | // Send output of RequestJS stream to ClamAV.
286 | // Send output of RequestJS to `some_local_file` if ClamAV receives data successfully
287 | input.pipe(av).pipe(output);
288 |
289 | // What happens when scan is completed
290 | av.on('scan-complete', result => {
291 | const {is_infected, viruses} = result;
292 | // Do stuff if you want
293 | });
294 |
295 | // What happens when data has been fully written to `output`
296 | output.on('finish', () => {
297 | // Do stuff if you want
298 | });
299 | } catch (err) {
300 | // Handle any error
301 | }
302 | })();
303 | ```
304 |
305 | ## 1.2.0
306 |
307 | ### SECURITY PATCH
308 |
309 | An important security patch was released in this version which fixes a bug causing false negatives in specific edge cases. Please upgrade immediately and only use this version from this point on.
310 |
311 | All older versions of this package have been deprecated on NPM.
312 |
313 | ## 1.3.0
314 |
315 | This just has some bug fixes and updates to dependencies. Technically, a new `'timeout'` event was added to the `passthrough` stream method, but, its not fully fleshed out and doesn't seem to work so it will remain undocumented for now.
316 |
317 | ## 1.4.0
318 |
319 | - Updated Mocha to v8.1.1. Subsequently, the oldest version of NodeJS allowed for this module is now v10.12.0.
320 | - Fixed issue with the method not throwing errors when testing existence and viability of remote/local socket.
321 |
322 | ## 1.4.1
323 |
324 | All sockets clients should now close when they are done being used, fail, or timeout.
325 |
326 | ## 1.4.2
327 |
328 | - Fixed initialization to pass a config-file option during clamav version check
329 | - Added new contributor
330 | - Fixed tests
331 |
332 | ## Newer Versions
333 |
334 | Please see the [GitHub Release page](https://github.com/kylefarris/clamscan/releases) for this project to see changelog info starting with v2.0.0.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Kyle Farris
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all
2 | TESTS = tests/index.js
3 |
4 | all:
5 | @npm install
6 |
7 | test: all
8 | @mkdir -p tests/infected
9 | @mkdir -p tests/bad_scan_dir
10 | @mkdir -p tests/mixed_scan_dir/folder1
11 | @mkdir -p tests/mixed_scan_dir/folder2
12 | @touch tests/clamscan-log
13 | @./node_modules/.bin/mocha --exit --trace-warnings --trace-deprecation --retries 1 --full-trace --timeout 5000 --check-leaks --reporter spec $(TESTS)
14 |
15 | clean:
16 | rm -rf node_modules
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NodeJS Clamscan Virus Scanning Utility
2 |
3 | [![NPM Version][npm-version-image]][npm-url] [![NPM Downloads][npm-downloads-image]][npm-url] [![Node.js Version][node-image]][node-url] [](https://github.com/kylefarris/clamscan/actions/workflows/test.yml)
4 |
5 | Use Node JS to scan files on your server with ClamAV's clamscan/clamdscan binary or via TCP to a remote server or local UNIX Domain socket. This is especially useful for scanning uploaded files provided by un-trusted sources.
6 |
7 | # !!IMPORTANT
8 |
9 | If you are using a version prior to 1.2.0, please upgrade! There was a security vulnerability in previous versions that can cause false negative in some edge cases. Specific details on how the attack could be implemented will not be disclosed here. Please update to 1.2.0 or greater ASAP. No breaking changes are included, only the security patch.
10 |
11 | All older versions in NPM have been deprecated.
12 |
13 | # Version 1.0.0 Information
14 |
15 | If you are migrating from v0.8.5 or less to v1.0.0 or greater, please read the [release notes](https://github.com/kylefarris/clamscan/releases/tag/v1.0.0) as there are some breaking changes (but also some awesome new features!).
16 |
17 | # Table of Contents
18 |
19 | - [Dependencies](#dependencies)
20 | - [Local Binary Method](#to-use-local-binary-method-of-scanning)
21 | - [TCP/Domain Socket Method](#to-use-clamav-using-tcp-sockets)
22 | - [How to Install](#how-to-install)
23 | - [License Info](#license-info)
24 | - [Getting Started](#getting-started)
25 | - [A note about using this module via sockets or TCP](#a-note-about-using-this-module-via-sockets-or-tcp)
26 | - [Basic Usage Example](#basic-usage-example)
27 | - [API](#api)
28 | - [getVersion](#getVersion)
29 | - [isInfected (alias: scanFile)](#isInfected)
30 | - [scanDir](#scanDir)
31 | - [scanFiles](#scanFiles)
32 | - [scanStream](#scanStream)
33 | - [passthrough](#passthrough)
34 | - [ping](#ping)
35 | - [Contribute](#contribute)
36 | - [Resources used to help develop this module](#resources-used-to-help-develop-this-module)
37 |
38 | # Dependencies
39 |
40 | ## To use local binary method of scanning
41 |
42 | You will need to install ClamAV's clamscan binary and/or have clamdscan daemon running on your server. On linux, it's quite simple.
43 |
44 | Fedora-based distros:
45 |
46 | ```bash
47 | sudo yum install clamav
48 | ```
49 |
50 | Debian-based distros:
51 |
52 | ```bash
53 | sudo apt-get install clamav clamav-daemon
54 | ```
55 |
56 | For OS X, you can install clamav with brew:
57 |
58 | ```bash
59 | sudo brew install clamav
60 | ```
61 |
62 | ## To use ClamAV using TCP sockets
63 |
64 | You will need access to either:
65 |
66 | 1. A local UNIX Domain socket for a local instance of `clamd`
67 |
68 | - Follow instructions in [To use local binary method of scanning](#user-content-to-use-local-binary-method-of-scanning).
69 | - Socket file is usually: `/var/run/clamd.scan/clamd.sock`
70 | - Make sure `clamd` is running on your local server
71 |
72 | 2. A local/remote `clamd` daemon
73 |
74 | - Must know the port the daemon is running on
75 | - If running on remote server, you must have the IP address/domain name
76 | - If running on remote server, it's firewall must have the appropriate TCP port(s) open
77 | - Make sure `clamd` is running on your local/remote server
78 |
79 | **NOTE:** This module is not intended to work on a Windows server. This would be a welcome addition if someone wants to add that feature (I may get around to it one day but have no urgent need for this).
80 |
81 | # How to Install
82 |
83 | ```bash
84 | npm install clamscan
85 | ```
86 |
87 | # License Info
88 |
89 | Licensed under the MIT License:
90 |
91 | -
92 |
93 | # Getting Started
94 |
95 | All of the values listed in the example below represent the default values for their respective configuration item.
96 |
97 | You can simply do this:
98 |
99 | ```javascript
100 | const NodeClam = require('clamscan');
101 | const ClamScan = new NodeClam().init();
102 | ```
103 |
104 | And, you'll be good to go.
105 |
106 | **BUT**: If you want more control, you can specify all sorts of options.
107 |
108 | ```javascript
109 | const NodeClam = require('clamscan');
110 | const ClamScan = new NodeClam().init({
111 | removeInfected: false, // If true, removes infected files
112 | quarantineInfected: false, // False: Don't quarantine, Path: Moves files to this place.
113 | scanLog: null, // Path to a writeable log file to write scan results into
114 | debugMode: false, // Whether or not to log info/debug/error msgs to the console
115 | fileList: null, // path to file containing list of files to scan (for scanFiles method)
116 | scanRecursively: true, // If true, deep scan folders recursively
117 | clamscan: {
118 | path: '/usr/bin/clamscan', // Path to clamscan binary on your server
119 | db: null, // Path to a custom virus definition database
120 | scanArchives: true, // If true, scan archives (ex. zip, rar, tar, dmg, iso, etc...)
121 | active: true // If true, this module will consider using the clamscan binary
122 | },
123 | clamdscan: {
124 | socket: false, // Socket file for connecting via TCP
125 | host: false, // IP of host to connect to TCP interface
126 | port: false, // Port of host to use when connecting via TCP interface
127 | timeout: 60000, // Timeout for scanning files
128 | localFallback: true, // Use local preferred binary to scan if socket/tcp fails
129 | path: '/usr/bin/clamdscan', // Path to the clamdscan binary on your server
130 | configFile: null, // Specify config file if it's in an unusual place
131 | multiscan: true, // Scan using all available cores! Yay!
132 | reloadDb: false, // If true, will re-load the DB on every call (slow)
133 | active: true, // If true, this module will consider using the clamdscan binary
134 | bypassTest: false, // Check to see if socket is available when applicable
135 | tls: false, // Use plaintext TCP to connect to clamd
136 | },
137 | preference: 'clamdscan' // If clamdscan is found and active, it will be used by default
138 | });
139 | ```
140 |
141 | Here is a _non-default values example_ (to help you get an idea of what proper-looking values could be):
142 |
143 | ```javascript
144 | const NodeClam = require('clamscan');
145 | const ClamScan = new NodeClam().init({
146 | removeInfected: true, // Removes files if they are infected
147 | quarantineInfected: '~/infected/', // Move file here. removeInfected must be FALSE, though.
148 | scanLog: '/var/log/node-clam', // You're a detail-oriented security professional.
149 | debugMode: true, // This will put some debug info in your js console
150 | fileList: '/home/webuser/scanFiles.txt', // path to file containing list of files to scan
151 | scanRecursively: false, // Choosing false here will save some CPU cycles
152 | clamscan: {
153 | path: '/usr/bin/clam', // I dunno, maybe your clamscan is just call "clam"
154 | scanArchives: false, // Choosing false here will save some CPU cycles
155 | db: '/usr/bin/better_clam_db', // Path to a custom virus definition database
156 | active: false // you don't want to use this at all because it's evil
157 | },
158 | clamdscan: {
159 | socket: '/var/run/clamd.scan/clamd.sock', // This is pretty typical
160 | host: '127.0.0.1', // If you want to connect locally but not through socket
161 | port: 12345, // Because, why not
162 | timeout: 300000, // 5 minutes
163 | localFallback: false, // Do no fail over to binary-method of scanning
164 | path: '/bin/clamdscan', // Special path to the clamdscan binary on your server
165 | configFile: '/etc/clamd.d/daemon.conf', // A fairly typical config location
166 | multiscan: false, // You hate speed and multi-threaded awesome-sauce
167 | reloadDb: true, // You want your scans to run slow like with clamscan
168 | active: false, // you don't want to use this at all because it's evil
169 | bypassTest: true, // Don't check to see if socket is available. You should probably never set this to true.
170 | tls: true, // Connect to clamd over TLS
171 | },
172 | preference: 'clamscan' // If clamscan is found and active, it will be used by default
173 | });
174 | ```
175 |
176 | NOTE: If a valid `port` is provided but no `host` value is provided, the clamscan will assume `'localhost'` for `host`.
177 |
178 | ## A note about using this module via sockets or TCP
179 |
180 | As of version v1.0.0, this module supports communication with a local or remote ClamAV daemon through Unix Domain sockets or a TCP host/port combo. If you supply both in your configuration object, the UNIX Domain socket option will be used. The module _will not_ not fallback to using the alternative Host/Port method. If you wish to connect via Host/Port and not a Socket, please either omit the `socket` property in the config object or use `socket: null`.
181 |
182 | If you specify a valid clamscan/clamdscan binary in your config and you set `clamdscan.localFallback: true` in your config, this module _will_ fallback to the traditional way this module has worked--using a binary directly/locally.
183 |
184 | Also, there are some caveats to using the socket/tcp based approach:
185 |
186 | - The following configuration items are not honored (unless the module falls back to binary method):
187 |
188 | - `removeInfected` - remote clamd service config will dictate this
189 | - `quarantineInfected` - remote clamd service config will dictate this
190 | - `scanLog` - remote clamd service config will dictate this
191 | - `fileList` - this simply won't be available
192 | - `clamscan.db` - only available on fallback
193 | - `clamscan.scanArchives` - only available on fallback
194 | - `clamscan.path` - only available on fallback
195 | - `clamdscan.configFile` - only available on fallback
196 | - `clamdscan.path` - only available on fallback
197 |
198 | # Basic Usage Example
199 |
200 | For the sake of brevity, all the examples in the [API](#api) section will be shortened to just the relevant parts related specifically to that example. In those examples, we'll assume you already have an instance of the `clamscan` object. Since initializing the module returns a promise, you'll have to resolve that promise to get an instance of the `clamscan` object.
201 |
202 | **Below is the _full_ example of how you could get that instance and run some methods:**
203 |
204 | ```javascript
205 | const NodeClam = require('clamscan');
206 | const ClamScan = new NodeClam().init(options);
207 |
208 | // Get instance by resolving ClamScan promise object
209 | ClamScan.then(async clamscan => {
210 | try {
211 | // You can re-use the `clamscan` object as many times as you want
212 | const version = await clamscan.getVersion();
213 | console.log(`ClamAV Version: ${version}`);
214 |
215 | const {isInfected, file, viruses} = await clamscan.isInfected('/some/file.zip');
216 | if (isInfected) console.log(`${file} is infected with ${viruses}!`);
217 | } catch (err) {
218 | // Handle any errors raised by the code in the try block
219 | }
220 | }).catch(err => {
221 | // Handle errors that may have occurred during initialization
222 | });
223 | ```
224 |
225 | **If you're writing your code within an async function, getting an instance can be one less step:**
226 |
227 | ```javascript
228 | const NodeClam = require('clamscan');
229 |
230 | async some_function() {
231 | try {
232 | // Get instance by resolving ClamScan promise object
233 | const clamscan = await new NodeClam().init(options);
234 | const {goodFiles, badFiles} = await clamscan.scanDir('/foo/bar');
235 | } catch (err) {
236 | // Handle any errors raised by the code in the try block
237 | }
238 | }
239 |
240 | some_function();
241 | ```
242 |
243 | # API
244 |
245 | Complete/functional examples for various use-cases can be found in the [examples folder](https://github.com/kylefarris/clamscan/tree/master/examples).
246 |
247 |
248 |
249 | ## .getVersion([callback])
250 |
251 | This method allows you to determine the version of ClamAV you are interfacing with. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned.
252 |
253 | ### Parameters
254 |
255 | - `callback` (function) (optional) Will be called when the scan is complete. It receives 2 parameters:
256 |
257 | - `err` (object or null) A standard javascript Error object (null if no error)
258 | - `version` (string) The version of the clamav server you're interfacing with
259 |
260 | ### Returns
261 |
262 | - Promise
263 |
264 | - Promise resolution returns: `version` (string) The version of the clamav server you're interfacing with
265 |
266 | ### Callback Example
267 |
268 | ```javascript
269 | clamscan.getVersion((err, version) => {
270 | if (err) return console.error(err);
271 | console.log(`ClamAV Version: ${version}`);
272 | });
273 | ```
274 |
275 | ### Promise Example
276 |
277 | ```javascript
278 | clamscan.getVersion().then(version => {
279 | console.log(`ClamAV Version: ${version}`);
280 | }).catch(err => {
281 | console.error(err);
282 | });
283 | ```
284 |
285 |
286 |
287 | ## .isInfected(filePath[,callback])
288 |
289 | This method allows you to scan a single file. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method will likely be the most common use-case for this module.
290 |
291 | ### Alias
292 |
293 | `.scan_file`
294 |
295 | ### Parameters
296 |
297 | - `filePath` (string) Represents a path to the file to be scanned.
298 | - `callback` (function) (optional) Will be called when the scan is complete. It takes 3 parameters:
299 |
300 | - `err` (object or null) A standard javascript Error object (null if no error)
301 | - `file` (string) The original `filePath` passed into the `isInfected` method.
302 | - `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
303 | - `viruses` (array) An array of any viruses found in the scanned file.
304 |
305 | ### Returns
306 |
307 | - Promise
308 |
309 | - Promise resolution returns: `result` (object):
310 |
311 | - `file` (string) The original `filePath` passed into the `isInfected` method.
312 | - `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
313 | - `viruses` (array) An array of any viruses found in the scanned file.
314 |
315 | ### Callback Example
316 |
317 | ```javascript
318 | clamscan.isInfected('/a/picture/for_example.jpg', (err, file, isInfected, viruses) => {
319 | if (err) return console.error(err);
320 |
321 | if (isInfected) {
322 | console.log(`${file} is infected with ${viruses.join(', ')}.`);
323 | }
324 | });
325 | ```
326 |
327 | ### Promise Example
328 |
329 | ```javascript
330 | clamscan.isInfected('/a/picture/for_example.jpg').then(result => {
331 | const {file, isInfected, viruses} = result;
332 | if (isInfected) console.log(`${file} is infected with ${viruses.join(', ')}.`);
333 | }).then(err => {
334 | console.error(err);
335 | })
336 | ```
337 |
338 | ### Async/Await Example
339 |
340 | ```javascript
341 | const {file, isInfected, viruses} = await clamscan.isInfected('/a/picture/for_example.jpg');
342 | ```
343 |
344 |
345 |
346 | ## .scanDir(dirPath[,endCallback[,fileCallback]])
347 |
348 | Allows you to scan an entire directory for infected files. This obeys your `recursive` option even for `clamdscan` which does not have a native way to turn this feature off. If you have multiple paths, send them in an array to `scanFiles`.
349 |
350 | **TL;DR:** For maximum speed, don't supply a `fileCallback`.
351 |
352 | If you choose to supply a `fileCallback`, the scan will run a little bit slower (depending on number of files to be scanned) for `clamdscan`. If you are using `clamscan`, while it will work, I'd highly advise you to NOT pass a `fileCallback`... it will run incredibly slow.
353 |
354 | ### NOTE
355 |
356 | The `goodFiles` parameter of the `endCallback` callback in this method will only contain the directory that was scanned in **all** **but** the following scenarios:
357 |
358 | - A `fileCallback` callback is provided, and `scanRecursively` is set to _true_.
359 | - The scanner is set to `clamdscan` and `scanRecursively` is set to _false_.
360 | - The scanned directory contains 1 or more viruses. In this case, the `goodFiles` array will be empty.
361 |
362 | There will, however, be a total count of the good files which is calculated by determining the total number of files scanned and subtracting the number of bad files from that count. We simply can't provide a list of all good files due to the potential large memory usage implications of scanning a directory with, for example, _millions_ of files.
363 |
364 | ### Parameters
365 |
366 | - `dirPath` (string) (required) Full path to the directory to scan.
367 | - `endCallback` (function) (optional) Will be called when the entire directory has been completely scanned. This callback takes 3 parameters:
368 |
369 | - `err` (object) A standard javascript Error object (null if no error)
370 | - `goodFiles` (array) An *empty* array if path is _infected_. An array containing the directory name that was passed in if _clean_.
371 | - `badFiles` (array) List of the full paths to all files that are _infected_.
372 | - `viruses` (array) List of all the viruses found (feature request: associate to the bad files).
373 | - `numGoodFiles` (number) Number of files that were found to be clean.
374 |
375 | - `fileCallback` (function) (optional) Will be called after each file in the directory has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:
376 |
377 | - `err` (object or null) A standard Javascript Error object (null if no error)
378 | - `file` (string) Path to the file that just got scanned.
379 | - `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan file.
380 |
381 | ### Returns
382 |
383 | - Promise
384 |
385 | - Promise resolution returns: `result` (object):
386 |
387 | - `path` (string) The original `dir_path` passed into the `scanDir` method.
388 | - `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
389 | - `goodFiles` (array) An *empty* array if path is _infected_. An array containing the directory name that was passed in if _clean_.
390 | - `badFiles` (array) List of the full paths to all files that are _infected_.
391 | - `viruses` (array) List of all the viruses found (feature request: associate to the bad files).
392 | - `numGoodFiles` (number) Number of files that were found to be clean.
393 |
394 | ### Callback Example
395 |
396 | ```javascript
397 | clamscan.scanDir('/some/path/to/scan', (err, goodFiles, badFiles, viruses, numGoodFiles) {
398 | if (err) return console.error(err);
399 |
400 | if (badFiles.length > 0) {
401 | console.log(`${path} was infected. The offending files (${badFiles.join (', ')}) have been quarantined.`);
402 | console.log(`Viruses Found: ${viruses.join(', ')}`);
403 | } else {
404 | console.log(`${goodFiles[0]} looks good! ${numGoodFiles} file scanned and no problems found!.`);
405 | }
406 | });
407 | ```
408 |
409 | ### Promise Example
410 |
411 | ```javascript
412 | clamscan.scanDir('/some/path/to/scan').then(results => {
413 | const { path, isInfected, goodFiles, badFiles, viruses, numGoodFiles } = results;
414 | //...
415 | }).catch(err => {
416 | return console.error(err);
417 | });
418 | ```
419 |
420 | ### Async/Await Example
421 |
422 | ```javascript
423 | const { path, isInfected, goodFiles, badFiles, viruses, numGoodFiles } = await clamscan.scanDir('/some/path/to/scan');
424 | ```
425 |
426 |
427 |
428 | ## .scanFiles(files[,endCallback[,fileCallback]])
429 |
430 | This allows you to scan many files that might be in different directories or maybe only certain files of a single directory. This is essentially a wrapper for `isInfected` that simplifies the process of scanning many files or directories.
431 |
432 | ### Parameters
433 |
434 | - `files` (array) (optional) A list of strings representing full paths to files you want scanned. If not supplied, the module will check for a `fileList` config option. If neither is found, the method will throw an error.
435 | - `endCallback` (function) (optional) Will be called when the entire list of files has been completely scanned. This callback takes 3 parameters:
436 |
437 | - `err` (object or null) A standard JavaScript Error object (null if no error)
438 | - `goodFiles` (array) List of the full paths to all files that are _clean_.
439 | - `badFiles` (array) List of the full paths to all files that are _infected_.
440 |
441 | - `fileCallback` (function) (optional) Will be called after each file in the list has been scanned. This is useful for keeping track of the progress of the scan. This callback takes 3 parameters:
442 |
443 | - `err` (object or null) A standard JavaScript Error object (null if no error)
444 | - `file` (string) Path to the file that just got scanned.
445 | - `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan file.
446 |
447 | ### Returns
448 |
449 | - Promise
450 |
451 | - Promise resolution returns: `result` (object):
452 |
453 | - `goodFiles` (array) List of the full paths to all files that are _clean_.
454 | - `badFiles` (array) List of the full paths to all files that are _infected_.
455 | - `errors` (object) Per-file errors keyed by the filename in which the error happened. (ex. `{'foo.txt': Error}`)
456 | - `viruses` (array) List of all the viruses found (feature request: associate to the bad files).
457 |
458 | ### Callback Example
459 |
460 | ```javascript
461 | const scan_status = { good: 0, bad: 0 };
462 | const files = [
463 | '/path/to/file/1.jpg',
464 | '/path/to/file/2.mov',
465 | '/path/to/file/3.rb'
466 | ];
467 | clamscan.scanFiles(files, (err, goodFiles, badFiles, viruses) => {
468 | if (err) return console.error(err);
469 | if (badFiles.length > 0) {
470 | console.log({
471 | msg: `${goodFiles.length} files were OK. ${badFiles.length} were infected!`,
472 | badFiles,
473 | goodFiles,
474 | viruses,
475 | });
476 | } else {
477 | res.send({msg: "Everything looks good! No problems here!."});
478 | }
479 | }, (err, file, isInfected, viruses) => {
480 | ;(isInfected ? scan_status.bad++ : scan_status.good++);
481 | console.log(`${file} is ${(isInfected ? `infected with ${viruses}` : 'ok')}.`);
482 | console.log('Scan Status: ', `${(scan_status.bad + scan_status.good)}/${files.length}`);
483 | });
484 | ```
485 |
486 | ### Promise Example
487 |
488 | **Note:** There is currently no way to get per-file notifications with the Promise API.
489 |
490 | ```javascript
491 | clamscan.scanFiles(files).then(results => {
492 | const { goodFiles, badFiles, errors, viruses } = results;
493 | // ...
494 | }).catch(err => {
495 | console.error(err);
496 | })
497 | ```
498 |
499 | ### Async/Await Example
500 |
501 | ```javascript
502 | const { goodFiles, badFiles, errors, viruses } = await clamscan.scanFiles(files);
503 | ```
504 |
505 | #### Scanning files listed in fileList
506 |
507 | If this modules is configured with a valid path to a file containing a newline-delimited list of files, it will use the list in that file when scanning if the first paramter passed is falsy.
508 |
509 | **Files List Document:**
510 |
511 | ```bash
512 | /some/path/to/file.zip
513 | /some/other/path/to/file.exe
514 | /one/more/file/to/scan.rb
515 | ```
516 |
517 | **Script:**
518 |
519 | ```javascript
520 | const ClamScan = new NodeClam().init({
521 | fileList: '/path/to/fileList.txt'
522 | });
523 |
524 | ClamScan.then(async clamscan => {
525 | // Supply nothing to first parameter to use `fileList`
526 | const { goodFiles, badFiles, errors, viruses } = await clamscan.scanFiles();
527 | });
528 | ```
529 |
530 |
531 |
532 | ## .scanStream(stream[,callback])
533 |
534 | This method allows you to scan a binary stream. **NOTE**: This method will only work if you've configured the module to allow the use of a TCP or UNIX Domain socket. In other words, this will not work if you only have access to a local ClamAV binary.
535 |
536 | ### Parameters
537 |
538 | - `stream` (stream) A readable stream object
539 | - `callback` (function) (optional) Will be called after the stream has been scanned (or attempted to be scanned):
540 |
541 | - `err` (object or null) A standard JavaScript Error object (null if no error)
542 | - `isInfected` (boolean) **True**: Stream is infected; **False**: Stream is clean. **NULL**: Unable to scan file.
543 |
544 | ### Returns
545 |
546 | - Promise
547 |
548 | - Promise resolution returns: `result` (object):
549 |
550 | - `file` (string) **NULL** as no file path can be provided with the stream
551 | - `isInfected` (boolean) **True**: File is infected; **False**: File is clean. **NULL**: Unable to scan.
552 | - `viruses` (array) An array of any viruses found in the scanned file.
553 |
554 | ### Examples
555 |
556 | **Callback Example:**
557 |
558 | ```javascript
559 | const NodeClam = require('clamscan');
560 |
561 | // You'll need to specify your socket or TCP connection info
562 | const clamscan = new NodeClam().init({
563 | clamdscan: {
564 | socket: '/var/run/clamd.scan/clamd.sock',
565 | host: '127.0.0.1',
566 | port: 3310,
567 | }
568 | });
569 | const Readable = require('stream').Readable;
570 | const rs = Readable();
571 |
572 | rs.push('foooooo');
573 | rs.push('barrrrr');
574 | rs.push(null);
575 |
576 | clamscan.scanStream(stream, (err, { isInfected. viruses }) => {
577 | if (err) return console.error(err);
578 | if (isInfected) return console.log('Stream is infected! Booo!', viruses);
579 | console.log('Stream is not infected! Yay!');
580 | });
581 | ```
582 |
583 | **Promise Example:**
584 |
585 | ```javascript
586 | clamscan.scanStream(stream).then(({isInfected}) => {
587 | if (isInfected) return console.log("Stream is infected! Booo!");
588 | console.log("Stream is not infected! Yay!");
589 | }).catch(err => {
590 | console.error(err);
591 | };
592 | ```
593 |
594 | **Promise Example:**
595 |
596 | ```javascript
597 | const { isInfected, viruses } = await clamscan.scanStream(stream);
598 | ```
599 |
600 |
601 |
602 | ## .passthrough()
603 |
604 | The `passthrough` method returns a PassthroughStream object which allows you pipe a ReadbleStream through it and on to another output. In the case of this module's passthrough implementation, it's actually forking the data to also go to ClamAV via TCP or Domain Sockets. Each data chunk is only passed on to the output if that chunk was successfully sent to and received by ClamAV. The PassthroughStream object returned from this method has a special event that is emitted when ClamAV finishes scanning the streamed data so that you can decide if there's anything you need to do with the final output destination (ex. delete a file or S3 object).
605 |
606 | In typical, non-passthrough setups, a file is uploaded to the local filesytem and then subsequently scanned. With that setup, you have to wait for the upload to complete _and then wait again_ for the scan to complete. Using this module's `passthrough` method, you could theoretically speed up user uploads intended to be scanned by up to 2x because the files are simultaneously scanned and written to any WriteableStream output (examples: filesystem, S3, gzip, etc...).
607 |
608 | As for these theoretical gains, your mileage my vary and I'd love to hear feedback on this to see where things can still be improved.
609 |
610 | Please note that this method is different than all the others in that it returns a PassthroughStream object and does not support a Promise or Callback API. This makes sense once you see the example below (a practical working example can be found in the examples directory of this module):
611 |
612 | ### Example
613 |
614 | ```javascript
615 | const NodeClam = require('clamscan');
616 |
617 | // You'll need to specify your socket or TCP connection info
618 | const clamscan = new NodeClam().init({
619 | clamdscan: {
620 | socket: '/var/run/clamd.scan/clamd.sock',
621 | host: '127.0.0.1',
622 | port: 3310,
623 | }
624 | });
625 |
626 | // For example's sake, we're using the Axios module
627 | const axios = require('Axios');
628 |
629 | // Get a readable stream for a URL request
630 | const input = axios.get(some_url);
631 |
632 | // Create a writable stream to a local file
633 | const output = fs.createWriteStream(some_local_file);
634 |
635 | // Get instance of this module's PassthroughStream object
636 | const av = clamscan.passthrough();
637 |
638 | // Send output of Axios stream to ClamAV.
639 | // Send output of Axios to `some_local_file` if ClamAV receives data successfully
640 | input.pipe(av).pipe(output);
641 |
642 | // What happens when scan is completed
643 | av.on('scan-complete', result => {
644 | const { isInfected, viruses } = result;
645 | // Do stuff if you want
646 | });
647 |
648 | // What happens when data has been fully written to `output`
649 | output.on('finish', () => {
650 | // Do stuff if you want
651 | });
652 |
653 | // NOTE: no errors (or other events) are being handled in this example but standard errors will be emitted according to NodeJS's Stream specifications
654 | ```
655 |
656 |
657 |
658 | ## .ping()
659 |
660 | This method checks to see if the remote/local socket is working. It supports a callback and Promise API. If no callback is supplied, a Promise will be returned. This method can be used for healthcheck purposes and is already implicitly used during scan.
661 |
662 | ### Parameters
663 |
664 | - `callback` (function) (optional) Will be called after the ping:
665 |
666 | - `err` (object or null) A standard JavaScript Error object (null if no error)
667 | - `client` (object) A copy of the Socket/TCP client
668 |
669 | ### Returns
670 |
671 | - Promise
672 |
673 | - Promise resolution returns: `client` (object): A copy of the Socket/TCP client
674 |
675 | ### Examples
676 |
677 | **Callback Example:**
678 |
679 | ```javascript
680 | const NodeClam = require('clamscan');
681 |
682 | // You'll need to specify your socket or TCP connection info
683 | const clamscan = new NodeClam().init({
684 | clamdscan: {
685 | socket: '/var/run/clamd.scan/clamd.sock',
686 | host: '127.0.0.1',
687 | port: 3310,
688 | }
689 | });
690 |
691 | clamscan.ping((err, client) => {
692 | if (err) return console.error(err);
693 | console.log('ClamAV is still working!');
694 | client.end();
695 | });
696 | ```
697 |
698 | **Promise Example:**
699 |
700 | ```javascript
701 | clamscan.ping().then((client) => {
702 | console.log('ClamAV is still working!');
703 | client.end();
704 | }).catch(err => {
705 | console.error(err);
706 | };
707 | ```
708 |
709 | **Promise Example:**
710 |
711 | ```javascript
712 | const client = await clamscan.ping();
713 | client.end();
714 | ```
715 |
716 | # Contribute
717 |
718 | Got a missing feature you'd like to use? Found a bug? Go ahead and fork this repo, build the feature and issue a pull request.
719 |
720 | # Resources used to help develop this module
721 |
722 | -
723 | -
724 | -
725 | -
726 | -
727 |
728 | [node-image]: https://img.shields.io/node/v/clamscan.svg
729 | [node-url]: https://nodejs.org/en/download
730 | [npm-downloads-image]: https://img.shields.io/npm/dm/clamscan.svg
731 | [npm-url]: https://npmjs.org/package/clamscan
732 | [npm-version-image]: https://img.shields.io/npm/v/clamscan.svg
733 | [travis-image]: https://img.shields.io/travis/kylefarris/clamscan/master.svg
734 | [travis-url]: https://travis-ci.org/kylefarris/clamscan
735 |
--------------------------------------------------------------------------------
/examples/basic_async_await.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-extraneous-dependencies
2 | const axios = require('axios');
3 | const fs = require('fs');
4 |
5 | const fakeVirusUrl = 'https://secure.eicar.org/eicar_com.txt';
6 | const tempDir = __dirname;
7 | const scanFile = `${tempDir}/tmp_file.txt`;
8 |
9 | const config = {
10 | removeInfected: true,
11 | debugMode: false,
12 | scanRecursively: false,
13 | clamdscan: {
14 | path: '/usr/bin/clamdscan',
15 | // config_file: '/etc/clamd.d/daemon.conf'
16 | },
17 | preference: 'clamdscan',
18 | };
19 |
20 | // Initialize the clamscan module
21 | const NodeClam = require('../index'); // Offically: require('clamscan');
22 |
23 | (async () => {
24 | const clamscan = await new NodeClam().init(config);
25 | let body;
26 |
27 | // Request a test file from the internet...
28 | try {
29 | body = await axios.get(fakeVirusUrl);
30 | } catch (err) {
31 | if (err.response) console.err(`${err.response.status}: Request Failed. `, err.response.data);
32 | else if (err.request) console.error('Error with Request: ', err.request);
33 | else console.error('Error: ', err.message);
34 | process.exit(1);
35 | }
36 |
37 | // Write the file to the filesystem
38 | fs.writeFileSync(scanFile, body);
39 |
40 | // Scan the file
41 | try {
42 | const { file, isInfected, viruses } = await clamscan.isInfected(scanFile);
43 |
44 | // If `isInfected` is TRUE, file is a virus!
45 | if (isInfected === true) {
46 | console.log(
47 | `You've downloaded a virus (${viruses.join(
48 | ''
49 | )})! Don't worry, it's only a test one and is not malicious...`
50 | );
51 | } else if (isInfected === null) {
52 | console.log("Something didn't work right...");
53 | } else if (isInfected === false) {
54 | console.log(`The file (${file}) you downloaded was just fine... Carry on...`);
55 | }
56 |
57 | // Remove the file (for good measure)
58 | if (fs.existsSync(scanFile)) fs.unlinkSync(scanFile);
59 | process.exit(0);
60 | } catch (err) {
61 | console.error(`ERROR: ${err}`);
62 | console.trace(err.stack);
63 | process.exit(1);
64 | }
65 | })();
66 |
--------------------------------------------------------------------------------
/examples/passthrough.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-extraneous-dependencies
2 | const axios = require('axios');
3 | const fs = require('fs');
4 | const { promisify } = require('util');
5 |
6 | const fsUnlink = promisify(fs.unlink);
7 |
8 | // const fakeVirusUrl = 'https://secure.eicar.org/eicar_com.txt';
9 | const normalFileUrl = 'https://raw.githubusercontent.com/kylefarris/clamscan/sockets/README.md';
10 | // const largeFileUrl = 'http://speedtest-ny.turnkeyinternet.net/100mb.bin';
11 | const passthruFile = `${__dirname}/output`;
12 |
13 | const testUrl = normalFileUrl;
14 | // const testUrl = fakeVirusUrl;
15 | // const testUrl = largeFileUrl;
16 |
17 | // Initialize the clamscan module
18 | const NodeClam = require('../index'); // Offically: require('clamscan');
19 |
20 | /**
21 | * Removes whatever file was passed-through during the scan.
22 | */
23 | async function removeFinalFile() {
24 | try {
25 | await fsUnlink(passthruFile);
26 | console.log(`Output file: "${passthruFile}" was deleted.`);
27 | process.exit(1);
28 | } catch (err) {
29 | console.error(err);
30 | process.exit(1);
31 | }
32 | }
33 |
34 | /**
35 | * Actually run the example code.
36 | */
37 | async function test() {
38 | const clamscan = await new NodeClam().init({
39 | debugMode: true,
40 | clamdscan: {
41 | host: 'localhost',
42 | port: 3310,
43 | bypassTest: true,
44 | // socket: '/var/run/clamd.scan/clamd.sock',
45 | },
46 | });
47 |
48 | const input = axios.get(testUrl);
49 | const output = fs.createWriteStream(passthruFile);
50 | const av = clamscan.passthrough();
51 |
52 | input.pipe(av).pipe(output);
53 |
54 | av.on('error', (error) => {
55 | if ('data' in error && error.data.isInfected) {
56 | console.error('Dang, your stream contained a virus(es):', error.data.viruses);
57 | } else {
58 | console.error(error);
59 | }
60 | removeFinalFile();
61 | })
62 | .on('timeout', () => {
63 | console.error('It looks like the scanning has timedout.');
64 | process.exit(1);
65 | })
66 | .on('finish', () => {
67 | console.log('All data has been sent to virus scanner');
68 | })
69 | .on('end', () => {
70 | console.log('All data has been scanned sent on to the destination!');
71 | })
72 | .on('scan-complete', (result) => {
73 | console.log('Scan Complete: Result: ', result);
74 | if (result.isInfected === true) {
75 | console.log(
76 | `You've downloaded a virus (${result.viruses.join(
77 | ', '
78 | )})! Don't worry, it's only a test one and is not malicious...`
79 | );
80 | } else if (result.isInfected === null) {
81 | console.log(`There was an issue scanning the file you downloaded...`);
82 | } else {
83 | console.log(`The file (${testUrl}) you downloaded was just fine... Carry on...`);
84 | }
85 | removeFinalFile();
86 | process.exit(0);
87 | });
88 |
89 | output.on('finish', () => {
90 | console.log('Data has been fully written to the output...');
91 | output.destroy();
92 | });
93 |
94 | output.on('error', (error) => {
95 | console.log('Final Output Fail: ', error);
96 | process.exit(1);
97 | });
98 | }
99 |
100 | test();
101 |
--------------------------------------------------------------------------------
/examples/pipe2s3.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable consistent-return */
2 | /* eslint-disable import/no-extraneous-dependencies */
3 | /* eslint-disable import/no-unresolved */
4 | const EventEmitter = require('events');
5 | const filesize = require('filesize');
6 | const { uuidv4 } = require('uuid');
7 | const NodeClam = require('clamscan');
8 | const BusBoy = require('busboy');
9 | const AWS = require('aws-sdk');
10 |
11 | AWS.config.region = '';
12 |
13 | const ClamScan = new NodeClam().init({
14 | removeInfected: true,
15 | scanRecursively: false,
16 | clamdscan: {
17 | socket: '/var/run/clamd.scan/clamd.sock',
18 | timeout: 300000,
19 | localFallback: true,
20 | },
21 | preference: 'clamdscan',
22 | });
23 |
24 | const s3Config = {
25 | params: {
26 | Bucket: '',
27 | },
28 | };
29 | const s3 = new AWS.S3(s3Config);
30 | const s3Stream = require('s3-upload-stream')(s3);
31 |
32 | /**
33 | * Example method for taking an end-user's upload stream and piping it though
34 | * clamscan and then on to S3 with full error-handling. This method assumes
35 | * you're using ExpressJS as your server.
36 | *
37 | * NOTE: This method can only handle one file in a request payload.
38 | *
39 | * @param {object} req - An Express Request object
40 | * @param {object} res - An Express Response object
41 | * @param {object} [opts] - Used to override defaults
42 | * @returns {Promise