ScanCan provides a light weight RESTful API for ClamAV (https://docs.clamav.net/) in a container.
docker build -t scancan .
docker rm scancan;docker run -it --volume=/pathtoclamvirusdefs:/opt/clamav --volume=/pathtoamountyouwanttoscan:/opt/files -p portyouwant:8080 --name scancan scancan
You should then update the virus definitions on the mount using freshclam (https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam). ClamAV will pick up the new definitions automatically.
docker build --build-arg basev=cron -t scancan .
docker rm scancan;docker run -it --volume=/pathtoamountyouwanttoscan:/opt/files -p portyouwant:8080 --name scancan scancan
Note there are issues with this approach:
- It's not well tested
- If you are running multiple containers you'll get blocked from downloading at the same time.
Please take these results for what they are worth...not much.
Running Apache Bench on a local container:
- Concurrency: 100
- Requests: 10000
- Requests scanned a 10 KB zip file on a mounted volume.
Results:
Concurrency Level: 100
Time taken for tests: 17.318 seconds
Complete requests: 10000
Failed requests: 0
Total transferred: 2000000 bytes
Total body sent: 2180000
HTML transferred: 560000 bytes
Requests per second: 577.43 [#/sec] (mean)
Time per request: 173.180 [ms] (mean)
Time per request: 1.732 [ms] (mean, across all concurrent requests)
Transfer rate: 112.78 [Kbytes/sec] received
122.93 kb/s sent
235.71 kb/s total
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.5 0 6
Processing: 12 172 13.6 169 242
Waiting: 3 162 13.7 160 236
Total: 12 173 13.5 170 243
WARNING: The median and mean for the initial connection time are not within a normal deviation
These results are probably not that reliable.
Percentage of the requests served within a certain time (ms)
50% 170
66% 173
75% 177
80% 181
90% 191
95% 199
98% 209
99% 218
100% 243 (longest request)
Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.
Virus Scanning API for ClamAV
Code samples
# You can also use wget
curl -X GET /health \
-H 'Accept: application/json'
GET /health HTTP/1.1
Accept: application/json
const headers = {
'Accept':'application/json'
};
fetch('/health',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Accept' => 'application/json'
}
result = RestClient.get '/health',
params: {
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Accept': 'application/json'
}
r = requests.get('/health', headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Accept' => 'application/json',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('GET','/health', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("/health");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "/health", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /health
Health
GET /health: determine the health of ScanCan Returns: result (object)
Example responses
200 Response
null
Status | Meaning | Description | Schema |
---|---|---|---|
200 | OK | Successful Response | Inline |
Code samples
# You can also use wget
curl -X POST /scanpath/{path} \
-H 'Accept: application/json'
POST /scanpath/{path} HTTP/1.1
Accept: application/json
const headers = {
'Accept':'application/json'
};
fetch('/scanpath/{path}',
{
method: 'POST',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Accept' => 'application/json'
}
result = RestClient.post '/scanpath/{path}',
params: {
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Accept': 'application/json'
}
r = requests.post('/scanpath/{path}', headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Accept' => 'application/json',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('POST','/scanpath/{path}', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("/scanpath/{path}");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "/scanpath/{path}", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
POST /scanpath/{path}
Scan Path
POST /scanpath: scan a mounted path with ClamAV Parameters: path (str): a path Returns: result (Object)
Name | In | Type | Required | Description |
---|---|---|---|---|
path | path | string | true | none |
Example responses
200 Response
null
Status | Meaning | Description | Schema |
---|---|---|---|
200 | OK | Successful Response | Inline |
422 | Unprocessable Entity | Validation Error | HTTPValidationError |
Code samples
# You can also use wget
curl -X GET /scanurl/?url=string \
-H 'Accept: application/json'
GET /scanurl/?url=string HTTP/1.1
Accept: application/json
const headers = {
'Accept':'application/json'
};
fetch('/scanurl/?url=string',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Accept' => 'application/json'
}
result = RestClient.get '/scanurl/',
params: {
'url' => 'string'
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Accept': 'application/json'
}
r = requests.get('/scanurl/', params={
'url': 'string'
}, headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Accept' => 'application/json',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('GET','/scanurl/', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("/scanurl/?url=string");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "/scanurl/", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /scanurl/
Scan Url
GET /scanurl: scan a url with ClamAV Parameters: url (str): a url Returns: result (Object)
Name | In | Type | Required | Description |
---|---|---|---|---|
url | query | string | true | none |
Example responses
200 Response
null
Status | Meaning | Description | Schema |
---|---|---|---|
200 | OK | Successful Response | Inline |
422 | Unprocessable Entity | Validation Error | HTTPValidationError |
Code samples
# You can also use wget
curl -X POST /contscan/{path} \
-H 'Accept: application/json'
POST /contscan/{path} HTTP/1.1
Accept: application/json
const headers = {
'Accept':'application/json'
};
fetch('/contscan/{path}',
{
method: 'POST',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Accept' => 'application/json'
}
result = RestClient.post '/contscan/{path}',
params: {
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Accept': 'application/json'
}
r = requests.post('/contscan/{path}', headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Accept' => 'application/json',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('POST','/contscan/{path}', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("/contscan/{path}");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"application/json"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "/contscan/{path}", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
POST /contscan/{path}
Cont Scan
POST /contscan: scan a mounted path with ClamAV, continue if found Parameters: path (str): a path Returns: result (Object)
Name | In | Type | Required | Description |
---|---|---|---|---|
path | path | string | true | none |
Example responses
200 Response
null
Status | Meaning | Description | Schema |
---|---|---|---|
200 | OK | Successful Response | Inline |
422 | Unprocessable Entity | Validation Error | HTTPValidationError |
Code samples
# You can also use wget
curl -X POST /scanfile \
-H 'Content-Type: multipart/form-data' \
-H 'Accept: application/json'
POST /scanfile HTTP/1.1
Content-Type: multipart/form-data
Accept: application/json
const inputBody = '{
"file": "string"
}';
const headers = {
'Content-Type':'multipart/form-data',
'Accept':'application/json'
};
fetch('/scanfile',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Content-Type' => 'multipart/form-data',
'Accept' => 'application/json'
}
result = RestClient.post '/scanfile',
params: {
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Content-Type': 'multipart/form-data',
'Accept': 'application/json'
}
r = requests.post('/scanfile', headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Content-Type' => 'multipart/form-data',
'Accept' => 'application/json',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('POST','/scanfile', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("/scanfile");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("POST");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Content-Type": []string{"multipart/form-data"},
"Accept": []string{"application/json"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("POST", "/scanfile", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
POST /scanfile
Scan Upload File
POST /scanfile: scan a file stream with ClamAV Parameters: file (bytes): an uploaded file Returns: result (Object)
Body parameter
file: string
Name | In | Type | Required | Description |
---|---|---|---|---|
body | body | Body_scan_upload_file_scanfile_post | true | none |
Example responses
200 Response
null
Status | Meaning | Description | Schema |
---|---|---|---|
200 | OK | Successful Response | Inline |
422 | Unprocessable Entity | Validation Error | HTTPValidationError |
Code samples
# You can also use wget
curl -X GET /license \
-H 'Accept: text/plain'
GET /license HTTP/1.1
Accept: text/plain
const headers = {
'Accept':'text/plain'
};
fetch('/license',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
require 'rest-client'
require 'json'
headers = {
'Accept' => 'text/plain'
}
result = RestClient.get '/license',
params: {
}, headers: headers
p JSON.parse(result)
import requests
headers = {
'Accept': 'text/plain'
}
r = requests.get('/license', headers = headers)
print(r.json())
<?php
require 'vendor/autoload.php';
$headers = array(
'Accept' => 'text/plain',
);
$client = new \GuzzleHttp\Client();
// Define array of request body.
$request_body = array();
try {
$response = $client->request('GET','/license', array(
'headers' => $headers,
'json' => $request_body,
)
);
print_r($response->getBody()->getContents());
}
catch (\GuzzleHttp\Exception\BadResponseException $e) {
// handle exception or api errors.
print_r($e->getMessage());
}
// ...
URL obj = new URL("/license");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
int responseCode = con.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
package main
import (
"bytes"
"net/http"
)
func main() {
headers := map[string][]string{
"Accept": []string{"text/plain"},
}
data := bytes.NewBuffer([]byte{jsonReq})
req, err := http.NewRequest("GET", "/license", data)
req.Header = headers
client := &http.Client{}
resp, err := client.Do(req)
// ...
}
GET /license
Show License
GET /license: view the ScanCan license Returns: license (string)
Example responses
200 Response
"string"
Status | Meaning | Description | Schema |
---|---|---|---|
200 | OK | Successful Response | string |
{
"file": "string"
}
Body_scan_upload_file_scanfile_post
Name | Type | Required | Restrictions | Description |
---|---|---|---|---|
file | string(binary) | true | none | none |
{
"detail": [
{
"loc": [
"string"
],
"msg": "string",
"type": "string"
}
]
}
HTTPValidationError
Name | Type | Required | Restrictions | Description |
---|---|---|---|---|
detail | [ValidationError] | false | none | none |
{
"loc": [
"string"
],
"msg": "string",
"type": "string"
}
ValidationError
Name | Type | Required | Restrictions | Description |
---|---|---|---|---|
loc | [anyOf] | true | none | none |
anyOf
Name | Type | Required | Restrictions | Description |
---|---|---|---|---|
» anonymous | string | false | none | none |
or
Name | Type | Required | Restrictions | Description |
---|---|---|---|---|
» anonymous | integer | false | none | none |
continued
Name | Type | Required | Restrictions | Description |
---|---|---|---|---|
msg | string | true | none | none |
type | string | true | none | none |