Metadata-Version: 2.4
Name: tns-antivirus
Version: 0.1.0
Summary: Reusable antivirus file scanning module using ClamAV
Author: Naveenkumar Koppala
Author-email: naveenkumar.k@tnsservices.com
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3.0.0,>=2.2.7
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: license
Dynamic: license-file
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# tns-antivirus

Reusable antivirus file scanning module using ClamAV. Scan files and get `clean` / `infected` results. All connection details passed at initialization â€” no hardcoded config, no env vars.

---

## Installation

```bash
pip install tns-antivirus
```

---

## Quick Start

```python
from antivirus import AntivirusScanner

scanner = AntivirusScanner(clamav_host="107.21.176.2", clamav_port=3310)

result = scanner.scan(file_bytes, filename="resume.pdf")

if result.is_clean:
    print("Safe to upload!")
elif result.is_infected:
    print(f"Virus detected: {result.virus_name}")
```

---

## How It Works

```
Your Service â†’ AntivirusScanner.scan(file_bytes) â†’ ClamAV Daemon (TCP)
                        â†“
                   ScanResult(is_clean=True/False)
```

- Sends file bytes to ClamAV daemon over raw TCP socket (`zINSTREAM` protocol)
- Validates file before scanning (dangerous extensions, empty files, size limits)
- Retries with exponential backoff if ClamAV is temporarily unavailable
- Returns a `ScanResult` â€” your service decides what to do with it

---

## Integration Example (FastAPI Service)

### Step 1: Add to `requirements.txt`

```
tns-antivirus==0.1.0
```

### Step 2: Add settings to `app/core/config.py`

```python
CLAMAV_HOST: str = "107.21.176.2"
CLAMAV_PORT: int = 3310
```

### Step 3: Create `app/core/antivirus_config.py`

```python
from antivirus import AntivirusScanner
from app.core.config import settings

scanner = AntivirusScanner(
    clamav_host=settings.CLAMAV_HOST,
    clamav_port=settings.CLAMAV_PORT,
)
```

### Step 4: Use in your route or service

```python
from fastapi import UploadFile
from app.core.antivirus_config import scanner

async def upload_document(file: UploadFile):
    file_bytes = await file.read()

    result = scanner.scan(file_bytes, filename=file.filename)

    if result.is_infected:
        return {"error": f"Virus detected: {result.virus_name}"}

    if not result.is_clean:
        return {"error": result.error}

    # File is safe â€” upload to S3, save to DB, etc.
    s3_url = upload_to_s3(file_bytes, key=f"uploads/{file.filename}")
    return {"file_url": s3_url, "scan_status": result.status}
```

---

## API Reference

### `AntivirusScanner(clamav_host, clamav_port, ...)`

| Parameter | Type | Default | Description |
|---|---|---|---|
| `clamav_host` | `str` | *required* | ClamAV daemon IP/hostname |
| `clamav_port` | `int` | `3310` | ClamAV daemon port |
| `timeout` | `int` | `5` | Socket timeout in seconds |
| `max_retries` | `int` | `3` | Retry attempts with exponential backoff |
| `max_file_size` | `int` | `104857600` | Max file size in bytes (default 100MB) |
| `dangerous_extensions` | `list` | see below | Blocked file extensions |

**Default dangerous extensions:** `.exe`, `.bat`, `.cmd`, `.scr`, `.com`, `.pif`, `.vbs`, `.js`, `.msi`, `.dll`

### Methods

| Method | Returns | Description |
|---|---|---|
| `scan(file_data, filename)` | `ScanResult` | Validate + scan file bytes |
| `is_healthy()` | `bool` | Check if ClamAV daemon is reachable |
| `get_version()` | `str \| None` | Get ClamAV version string |

### `ScanResult`

| Field | Type | Description |
|---|---|---|
| `status` | `str` | `"clean"`, `"infected"`, `"validation_failed"`, `"error"` |
| `is_clean` | `bool` | `True` if file passed all checks |
| `is_infected` | `bool` | `True` if virus found |
| `filename` | `str` | Original filename |
| `file_size` | `int` | File size in bytes |
| `virus_name` | `str \| None` | Virus name (only if infected) |
| `file_hash` | `str \| None` | SHA-256 hash of the file |
| `scan_time_ms` | `float` | Total scan time in milliseconds |
| `error` | `str \| None` | Error message (only if failed) |
| `timestamp` | `datetime` | When the scan was performed |

### Response Examples

**Clean file:**
```json
{
    "status": "clean",
    "is_clean": true,
    "is_infected": false,
    "filename": "resume.pdf",
    "file_size": 45231,
    "file_hash": "a3f2b8c1d4e5f6...",
    "scan_time_ms": 120.5,
    "virus_name": null,
    "error": null
}
```

**Infected file:**
```json
{
    "status": "infected",
    "is_clean": false,
    "is_infected": true,
    "filename": "bad.pdf",
    "file_size": 12000,
    "virus_name": "Eicar-Test-Signature",
    "scan_time_ms": 85.2,
    "error": null
}
```

**Validation failed (e.g., .exe file):**
```json
{
    "status": "validation_failed",
    "is_clean": false,
    "is_infected": false,
    "filename": "program.exe",
    "error": "Upload Failed - Dangerous file type detected: .exe"
}
```

**ClamAV unavailable:**
```json
{
    "status": "error",
    "is_clean": false,
    "is_infected": false,
    "filename": "doc.pdf",
    "error": "ClamAV unavailable after 3 attempts: Connection refused"
}
```

---

## Development & Deployment

### Prerequisites

```bash
pip install twine build
```

### Run Tests

```bash
pip install -e .
pip install pytest
pytest tests/ -v
```

### Deploy to PyPI

```bash
# Clean, build, and deploy
rm -rf dist/ build/ *.egg-info/
python setup.py sdist bdist_wheel
twine upload dist/*
```

---

## License

MIT
