Skip to content

Commit 42d315e

Browse files
author
evanwashere
committed
publish clamav
0 parents  commit 42d315e

File tree

4 files changed

+155
-0
lines changed

4 files changed

+155
-0
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
implements all non-deprecated commands in clamd
2+
3+
```js
4+
const clamav = new ClamAV({ port: 3310, host: '127.0.0.1' });
5+
const eicar = await fetch('https://secure.eicar.org/eicar.com.txt');
6+
7+
console.log(await clamav.version());
8+
console.log(await clamav.scan(eicar.body));
9+
console.log(await clamav.scan(new Uint8Array([...])));
10+
console.log(await clamav.scan(await Deno.open('file.txt')));
11+
```

lib.js

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { fromStreamReader } from "https://deno.land/[email protected]/io/mod.ts";
2+
3+
export const commands = {
4+
_: Deno.core.encode('\n'),
5+
ping: Deno.core.encode('nPING'),
6+
scan: Deno.core.encode('nSCAN '),
7+
stats: Deno.core.encode('nSTATS'),
8+
reload: Deno.core.encode('nRELOAD'),
9+
version: Deno.core.encode('nVERSION'),
10+
shutdown: Deno.core.encode('nSHUTDOWN'),
11+
contscan: Deno.core.encode('nCONTSCAN '),
12+
instream: Deno.core.encode('nINSTREAM\n'),
13+
multiscan: Deno.core.encode('nMULTISCAN '),
14+
allmatchscan: Deno.core.encode('nALLMATCHSCAN '),
15+
}
16+
17+
export default class ClamAV {
18+
constructor({ port, host }) {
19+
this.port = port;
20+
this.host = host;
21+
}
22+
23+
async ping() {
24+
return Deno.core.decode(await this.request(commands.ping));
25+
}
26+
27+
async stats() {
28+
return Deno.core.decode(await this.request(commands.stats));
29+
}
30+
31+
async reload() {
32+
return Deno.core.decode(await this.request(commands.reload));
33+
}
34+
35+
async version() {
36+
return Deno.core.decode(await this.request(commands.version));
37+
}
38+
39+
async contscan(path) {
40+
return Deno.core.decode(await this.request(commands.contscan, Deno.core.encode(path)));
41+
}
42+
43+
async multiscan(path) {
44+
return Deno.core.decode(await this.request(commands.multiscan, Deno.core.encode(path)));
45+
}
46+
47+
async allmatchscan(path) {
48+
return Deno.core.decode(await this.request(commands.allmatchscan, Deno.core.encode(path)));
49+
}
50+
51+
async shutdown() {
52+
const res = await this.request(commands.shutdown);
53+
54+
if (0 === res.length) return !!1;
55+
else throw Deno.core.decode(res);
56+
}
57+
58+
async scan(body) {
59+
if ('string' === typeof body) {
60+
return Deno.core.decode(await this.request(commands.scan, Deno.core.encode(body)));
61+
}
62+
63+
else if (ArrayBuffer.isView(body) & !(body instanceof Uint8Array)) body = new Uint8Array(body.buffer);
64+
else if (body instanceof ArrayBuffer || body instanceof SharedArrayBuffer) body = new Uint8Array(body);
65+
66+
const { readable, writable } = new ChunksStream();
67+
const res = this.request(commands.instream, readable);
68+
if (body instanceof ReadableStream) await body.pipeTo(writable);
69+
70+
else {
71+
const w = writable.getWriter();
72+
if (body instanceof Uint8Array) await w.write(body);
73+
else for await (const chunk of Deno.iter(body)) await w.write(chunk);
74+
75+
await w.close();
76+
}
77+
78+
return Deno.core.decode(await res);
79+
}
80+
81+
82+
async request(cmd, body) {
83+
const con = await Deno.connect({
84+
port: this.port,
85+
transport: 'tcp',
86+
hostname: this.host,
87+
}).catch(() => Promise.reject(new Error(`clamd is not running on ${this.host}:${this.port}`)));
88+
89+
await con.write(cmd);
90+
91+
if (body) {
92+
if (body instanceof Uint8Array) await Deno.writeAll(con, body);
93+
else await Deno.copy(body instanceof ReadableStream ? fromStreamReader(body.getReader()) : body, con);
94+
}
95+
96+
con.write(commands._);
97+
return (await Deno.readAll(con)).subarray(0, -1);
98+
}
99+
}
100+
101+
class ChunksStream extends TransformStream {
102+
static transformer = {
103+
flush: ChunksStream.flush,
104+
transform: ChunksStream.transform,
105+
};
106+
107+
constructor() {
108+
super(ChunksStream.transformer);
109+
}
110+
111+
static flush(controller) {
112+
controller.enqueue(new Uint8Array(4));
113+
}
114+
115+
static transform(chunk, controller) {
116+
const size = new Uint8Array(4);
117+
new DataView(size.buffer).setUint32(0, chunk.length, false);
118+
119+
controller.enqueue(size);
120+
controller.enqueue(chunk);
121+
}
122+
}

package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"license": "MIT",
3+
"version": "1.0.0",
4+
"name": "@evan/clamav",
5+
"author": "evanwashere <[email protected]>",
6+
"description": "ClamAV client for deno",
7+
8+
"main": "lib.js",
9+
"module": "lib.js",
10+
"files": ["lib.js", "README.md"],
11+
"keywords": ["deno", "clamd", "clamav"],
12+
"homepage": "https://github.com/evanwashere/clamav#readme",
13+
"bugs": { "url": "https://github.com/evanwashere/clamav/issues" },
14+
"repository": { "type": "git", "url": "git+https://github.com/evanwashere/clamav.git" }
15+
}

test.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import ClamAV from './lib.js';
2+
3+
const clamav = new ClamAV({ port: 3310, host: '127.0.0.1' });
4+
const eicar = await fetch('https://secure.eicar.org/eicar.com.txt');
5+
6+
console.log(await clamav.version());
7+
console.log(await clamav.scan(eicar.body));

0 commit comments

Comments
 (0)